/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const Number_2: number = 2;
const Number_3: number = 3;
const Number_4: number = 4;
const Number_5: number = 5;
const Number_6: number = 6;
const Number_7: number = 7;
const Number_8: number = 8;
const Number_9: number = 9;
const Number_10: number = 10;
const Number_11: number = 11;
const Number_12: number = 12;
const Number_13: number = 13;
const Number_14: number = 14;
const Number_15: number = 15;
const Number_16: number = 16;
const Number_17: number = 17;
const Number_18: number = 18;
const Number_19: number = 19;
const Number_20: number = 20;
const Number_21: number = 21;
const Number_22: number = 22;
const Number_23: number = 23;
const Number_24: number = 24;
const Number_25: number = 25;
const Number_26: number = 26;
const Number_27: number = 27;
const Number_28: number = 28;
const Number_29: number = 29;
const Number_30: number = 30;
const Number_31: number = 31;
const Number_32: number = 32;
const Number_33: number = 33;
const Number_34: number = 34;
const Number_35: number = 35;
const Number_36: number = 36;
const Number_37: number = 37;
const Number_38: number = 38;
const Number_39: number = 39;
const Number_40: number = 40;
const Number_41: number = 41;
const Number_42: number = 42;
const Number_43: number = 43;
const Number_44: number = 44;
const Number_45: number = 45;
const Number_46: number = 46;
const Number_47: number = 47;
const Number_48: number = 48;
const Number_49: number = 49;
const Number_50: number = 50;
const Number_51: number = 51;
const Number_52: number = 52;
const Number_53: number = 53;
const Number_54: number = 54;
const Number_55: number = 55;
const Number_56: number = 56;
const Number_57: number = 57;
const Number_58: number = 58;
const Number_59: number = 59;
const Number_60: number = 60;
const Number_61: number = 61;
const Number_62: number = 62;
const Number_63: number = 63;
const Number_64: number = 64;
const Number_65: number = 65;
const Number_74: number = 74;
const Number_75: number = 75;
const Number_82: number = 82;
const Number_84: number = 84;
const Number_88: number = 88;
const Number_90: number = 90;
const Number_91: number = 91;
const Number_93: number = 93;
const Number_95: number = 95;
const Number_96: number = 96;
const Number_97: number = 97;
const Number_98: number = 98;
const Number_99: number = 99;
const Number_100: number = 100;
const Number_101: number = 101;
const Number_102: number = 102;
const Number_105: number = 105;
const Number_107: number = 107;
const Number_108: number = 108;
const Number_106: number = 106;
const Number_110: number = 110;
const Number_111: number = 111;
const Number_114: number = 114;
const Number_115: number = 115;
const Number_116: number = 116;
const Number_120: number = 120;
const Number_123: number = 123;
const Number_125: number = 125;
const Number_126: number = 126;
const Number_127: number = 127;
const Number_128: number = 128;
const Number_139: number = 139;
const Number_160: number = 160;
const Number_176: number = 176;
const Number_180: number = 180;
const Number_200: number = 200;
const Number_246: number = 246;
const Number_250: number = 250;
const Number_254: number = 254;
const Number_251: number = 251;
const Number_247: number = 247;
const Number_1240: number = 1240;
const Number_1131: number = 1131;
const Number_32767: number = 32767;
const Number_33900: number = 33900;
const Number_32768: number = 32768;
const Number_32000: number = 32000;
const Number_72: number = 72;
const Number_255: number = 255;
const Number_256: number = 256;
const Number_257: number = 257;
const Number_258: number = 258;
const Number_270: number = 270;
const Number_360: number = 360;
const Number_500: number = 500;
const Number_512: number = 512;
const Number_799: number = 799;
const Number_841: number = 841;
const Number_1024: number = 1024;
const Number_1567: number = 1567;
const Number_1728: number = 1728;
const Number_2048: number = 2048;
const Number_2276: number = 2276;
const Number_2896: number = 2896;
const Number_3406: number = 3406;
const Number_3784: number = 3784;
const Number_4017: number = 4017;
const Number_5793: number = 5793;
const Number_8192: number = 8192;

const NumberFloat_100: number = 100.0;
const Number_ooo1: number = 0.001;
const Number_0001: number = 0.001
const Number_2o: number = 2.0;
const Number_8o: number = 8.0;

const Number_0x00ff: number = 0x00ff;
const Number_0x02: number = 0x02;
const Number_0x03: number = 0x03;
const Number_0x0590: number = 0x0590;
const Number_0x05f4: number = 0x05f4;
const Number_0x0600: number = 0x0600;
const Number_0x06ff: number = 0x06ff;
const Number_0x0700: number = 0x0700;
const Number_0x08AC: number = 0x08AC;
const Number_0x0E: number = 0x0E;
const Number_0x1F: number = 0x1F;
const Number_0x1FE: number = 0x1FE;
const Number_0x40: number = 0x40;
const Number_0x41: number = 0x41;
const Number_0x46: number = 0x46;
const Number_0x49: number = 0x49;
const Number_0x4A: number = 0x4A;
const Number_0x62: number = 0x62;
const Number_0x636F6C72: number = 0x636F6C72;
const Number_0x64: number = 0x64;
const Number_0x65: number = 0x65;
const Number_0x6A501A1A: number = 0x6A501A1A;
const Number_0x6A703263: number = 0x6A703263;
const Number_0x6A703268: number = 0x6A703268;
const Number_0x6F: number = 0x6F;
const Number_0x7: number = 0x7;
const Number_0x7F: number = 0x7F;
const Number_0x80: number = 0x80;
const Number_0x8000: number = 0x8000;
const Number_0x3: number = 0x3;
const Number_0x3F: number = 0x3F;
const Number_0x8F: number = 0x8F;
const Number_0xF: number = 0xF;
const Number_0x0F: number = 0x0F;
const Number_0xFF: number = 0xFF;
const Number_0xff: number = 0xff;
const Number_0xFF00: number = 0xFF00;
const Number_0xFF4F: number = 0xFF4F;
const Number_0xFF51: number = 0xFF51;
const Number_0xFF52: number = 0xFF52;
const Number_0xFF5C: number = 0xFF5C;
const Number_0xFF5D: number = 0xFF5D;
const Number_0xFF64: number = 0xFF64;
const Number_0xFF90: number = 0xFF90;
const Number_0xFF93: number = 0xFF93;
const Number_0xFFC0: number = 0xFFC0;
const Number_0xFFC2: number = 0xFFC2;
const Number_0xFFC4: number = 0xFFC4;
const Number_0xFFD0: number = 0xFFD0;
const Number_0xFFD7: number = 0xFFD7;
const Number_0xFFD8: number = 0xFFD8;
const Number_0xFFD9: number = 0xFFD9;
const Number_0xFFDA: number = 0xFFDA;
const Number_0xFFDB: number = 0xFFDB;
const Number_0xFFDD: number = 0xFFDD;
const Number_0xFFE0: number = 0xFFE0;
const Number_0xFFE1: number = 0xFFE1;
const Number_0xFFE2: number = 0xFFE2;
const Number_0xFFE3: number = 0xFFE3;
const Number_0xFFE4: number = 0xFFE4;
const Number_0xFFE5: number = 0xFFE5;
const Number_0xFFE6: number = 0xFFE6;
const Number_0xFFE7: number = 0xFFE7;
const Number_0xFFE8: number = 0xFFE8;
const Number_0xFFE9: number = 0xFFE9;
const Number_0xFFEA: number = 0xFFEA;
const Number_0xFFEB: number = 0xFFEB;
const Number_0xFFEC: number = 0xFFEC;
const Number_0xFFED: number = 0xFFED;
const Number_0xFFEE: number = 0xFFEE;
const Number_0xFFEF: number = 0xFFEF;
const Number_0xFFFE: number = 0xFFFE;
const Number_0xFFFF: number = 0xFFFF;
const Number_0x0D: number = 0x0D;
const Number_0x0A: number = 0x0A;
const Number_0x3C: number = 0x3C;
const Number_0x5B: number = 0x5B;
const Number_0x5D: number = 0x5D;
const Number_0x10000: number = 0x10000;
const Number_0x3FF: number = 0x3FF;
const Number_0xF8E9: number = 0xF8E9;
const Number_0xF6D9: number = 0xF6D9;
const Number_0x00A9: number = 0x00A9;
const Number_0x7FFF: number = 0x7FFF;
const Number_0x09: number = 0x09;
const Number_0x00: number = 0x00;
const Number_0x20: number = 0x20;
const Number_0xC0: number = 0xC0;
const Number_0x00010000: number = 0x00010000;
const Number_0x00020000: number = 0x00020000;
const Number_0x00030000: number = 0x00030000;
const Number_0x0B: number = 0x0B;
const Number_0x8B: number = 0x8B;
const Number_0x7f: number = 0x7f;
const Number_0xc: number = 0xc;
const Number_0xa: number = 0xa;
const Number_0xb: number = 0xb;
const Number_0xe: number = 0xe;
const Number_0xf: number = 0xf;
const Number_0x1c: number = 0x1c;
const Number_0x1d: number = 0x1d;
const Number_0x100: number = 0x100;
const Number_0x0f: number = 0x0f;
const Number_0x08: number = 0x08;
const Number_0xEE: number = 0xEE;
const Number_0x21: number = 0x21;
const Number_0x8: number = 0x8;
const Number_0x2: number = 0x2;
const Number_0x10: number = 0x10;
const Number_0x04: number = 0x04;
const Number_0x01: number = 0x01;
const Number_1e10: number = 1e10;
const Number_2e10: number = 2e10;
const Number_0x73: number = 0x73;
const Number_0x6C: number = 0x6C;
const Number_0x54: number = 0x54;
const Number_0x1000000: number = 0x1000000;
const Number_0x3E: number = 0x3E;

let pdf_file: string = "test.pdf";
let canvas_logs: string[][] = new Array<string[]>();

function buffer(s: string): Uint8Array {
  let b: Uint8Array = new Uint8Array(s.length).fill(0);
  let a: Uint8Array = new Uint8Array(b);
  for (let i = 0; i < s.length; ++i){
    a[i] = s.charCodeAt(i);
  }
  b = a;
  return b;
}

function hash(s: string): number {
  let h: number = 0;
  let i: number = 0;
  while (Number_4 * i - Number_3 < s.length) {
    let j: number = 0;
    while (j < Number_4 && i + j < s.length) {
      let index: number = i + j;
      let ascii: number = s.charCodeAt(index);
      h = (h + ascii << (Number_8 * j));
      j += 1;
    }
    i += Number_4;
  }
  return h;
}

class FakeWorker {
  onmessage?: (obj: EventMessage) => void;
  postMessage(obj: EventMessageData) {
    let message: EventMessage = new EventMessage();
    message.data = obj;
    if (this.onmessage !== null && this.onmessage !== undefined) {
      this.onmessage(message);
    }
  }
  terminate() {
  }
}

class PDFContext {
  _originalSave?: () => void;
  _originalRestore: () => void;
  _originalRotate: (a:number) => void;
  _originalScale?: (a:number, b:number) => void;
  _originalTranslate: (a:number, b:number) => void;
  _originalTransform?: (a:number, b:number, c:number, d:number, e:number, f:number) => void;

  _transformMatrix: number[] = new Array<number>();
  _transformStack: number[][] = new Array<number[]>();
  mozCurrentTransform: number[];
  mozCurrentTransformInverse: number[];

  lineWidth: number = 0.0;
  miterLimit: number = 0.0;
  lineJoin:string = '';
  lineCap:string = '';

  mozDash: number[] = new Array<number>();
  webkitLineDash: number[] = new Array<number>();
  mozDashOffset: number = 0.0;
  webkitLineDashOffset: number = 0.0;
  globalAlpha: number = 0.0;
  strokeStyle?: string;
  fillStyle?: Pattern | string;
  canvas?: Canvas;
  mozFillRule: string = '';
  font: string = '';

  __log__: string[] = new Array<string>();

  save(): void {
    this.__log__.push('save', '\n');
  }

  restore(): void {
    this.__log__.push('restore', '\n');
  }

  transform(a?: number, b?: number, c?: number, d?: number, e?: number, f?: number): void {
    this.__log__.push('transform', String(a), String(b), String(c), String(d), String(e), String(f), '\n');
  }

  translate(x: number, y: number): void {
    this.__log__.push('translate', String(x), String(y), '\n');
  }

  scale(x: number, y: number): void {
    this.__log__.push('scale', String(x), String(y), '\n');
  }

  rect(x: number, y: number, w: number, h: number): void {
    this.__log__.push('rect', String(x), String(y), String(w), String(h), '\n');
  }

  clip(): void {
    this.__log__.push('clip', '\n');
  }

  fill(): void {
    this.__log__.push('fill', '\n');
  }

  stroke(): void {
    this.__log__.push('stroke', '\n');
  }

  beginPath(): void {
    this.__log__.push('beginPath', '\n');
  }

  closePath(): void {
    this.__log__.push('closePath', '\n');
  }

  moveTo(x: number, y: number): void {
    this.__log__.push('moveTo', String(x), String(y), '\n');
  }

  lineTo(x: number, y: number): void {
    this.__log__.push('lineTo', String(x), String(y), '\n');
  }

  fillRect(x: number, y: number, w: number, h: number): void {
    this.__log__.push('fillRect', String(x), String(y), String(w), String(h), '\n');
  }

  fillText(s: string, x: number, y: number, w?: number): void {
    this.__log__.push('fillText', String(s), String(x), String(y), '\n');
  }

  strokeText(s: string, x: number, y: number, w?: number): void {
    this.__log__.push('strokeText', String(s), String(x), String(y), '\n');
  }

  getImageData(x: number, y: number, w: number, h: number): Image {
    this.__log__.push('getImageData', String(x), String(y), String(w), String(h),'\n');
    return new Image()
  }

  putImageData(data: Image, x: number, y: number): void {
    this.__log__.push('putImageData', '{...}', String(x), String(y), '\n');
  }

  drawImage(image: Canvas | Image, x: number, y: number): void {
    this.__log__.push('drawImage', '<elem>', String(x), String(y), '\n');
  }

  getParameter(name: string): null {
    this.__log__.push('getParameter', name, '\n');
    return null;
  }

  enable(): void {
    this.__log__.push('enable', '\n');
  }

  disable(): void {
    this.__log__.push('disable', '\n');
  }

  depthFunc(param: string): void {
    this.__log__.push('depthFunc', param, '\n');
  }

  clearColor(r: number, g: number, b: number, a: number): void {
    this.__log__.push('clearColor', String(r), String(g), String(b), String(a), '\n');
  }

  clear(m: string): void {
    this.__log__.push('clear', m, '\n');
  }

  clearRect(x: number, y: number, w: number, h: number): void {
    this.__log__.push('createRect', String(x), String(y), String(w), String(h), '\n');
  }

  rotate(param: number): void{
  }

  bezierCurveTo(a: number, b: number, c: number, d: number, e: number, f: number):void {
  }

  drawImageFunc(image: Image, destX: number, destY: number, destWidth?: number, destHeight?: number,
    srcX?: number, srcY?: number, srcWidth?: number, srcHeight?: number): void {
  }

  createLinearGradient(x0: number, y0: number, x1: number, y1: number): Gradient {
    return new Gradient();
  }

  createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): Gradient {
    return new Gradient();
  }

  createPattern(canvas: Canvas, type: string): Pattern{
    return new Pattern();
  }

}

class PDFElementSheet {
  cssRules: string[] = new Array<string>();
  insertRule(rule: string, length: number): void {
  }
}

class PDFElement {
  __listeners__: Record<string, Function> = {};
  element_type: string | null;
  parentNode: PDFElement | null;
  nodeName: string | null;
  nextSibling: PDFElement | null;
  childNodes: PDFElement[] | null;
  firstChild: PDFElement | null;
  textContent: string | null;
  attributes: Map<string,string> = new Map<string, string>();
  src:string | null;
  innerHTML:string | null;
  sheet: PDFElementSheet = new PDFElementSheet();

  constructor(type: string = "") {
    this.element_type = type;
  }

  addEventListener(name: string, listener: Function, flg: boolean = false): void {
    this.__listeners__[name] = listener;
  }

  removeEventListener(name: string, fn: string | string[] = null, flg: boolean = false): void {
    this.__listeners__[name] = null;
  }

  dispatchEvent(event: PDFEvent): void {
    let listener:(_event:PDFEvent) => void = this.__listeners__[event.name] as (_event:PDFEvent) => void;
    if (listener !== null && listener !== undefined ) {
      listener(event)
    }
  }

  getElementsByTagName(name: string):PDFElement[] | null {
    if (name === 'head') {
      return [new PDFElement()];
    }
    return null;
  }

  appendChild(child?:PDFElement): void {
  }

  setAttribute(key: string, value: string): void {
    this.attributes.set(key,value);
  }
  hasChildNodes(): boolean {
    if (this.childNodes.length > 0) {
      return true;
    } else {
      return false;
    }
  }
}

class CFFDict {
  keyToNameMap: Map<number, string>;
  nameToKeyMap: Map<string, number | number[]>;
  defaults: Map<number, number[] | number>;
  types: Map<number, string|string[]>;
  opcodes: Map<number, number[]>;
  order: number[];

  strings: CFFStrings;
  values: Map<number, number | number[]>;

  constructor(tables: TablesStruct,strings?: CFFStrings) {
    this.keyToNameMap = tables.keyToNameMap;
    this.nameToKeyMap = tables.nameToKeyMap;
    this.defaults = tables.defaults;
    this.types = tables.types as Map<number, string>;
    this.opcodes = tables.opcodes;
    this.values = new Map();
    this.order = tables.order;
    this.strings = strings;
  }

  public setByKey(key: number, value: number[]): boolean {
    if (!(this.keyToNameMap.has(key))) {
      return false;
    }
    if (value.length === 0) {
      return true;
    }
    let type: string = this.types.get(key) as string;
    if (type === 'num' || type === 'sid' || type === 'offset') {
      this.values.set(key,value[0]);
    } else {
      this.values.set(key,value);
    }
    return true;
  }

  public hasName(name: string): boolean {
    return this.values.has(this.nameToKeyMap.get(name) as number);
  }

  public getByName(name: string): number | number[] {
    if (!(this.nameToKeyMap.has(name))) {
      error(`Invalid dictionary name" ${name} "`);
    }
    let key: number = this.nameToKeyMap.get(name) as number;
    if (!(this.values.has(key))) {
      return this.defaults.get(key);
    }
    return this.values.get(key);
  }

  public removeByName(name: string): void {
    if (this.nameToKeyMap.get(name)) {
      this.values.delete(this.nameToKeyMap.get(name) as number);
    }
  }

  public static createTables(layout: (string | string[] | number[]|null|number)[][]): TablesStruct {
    let tables:TablesStruct = new TablesStruct()
    for (let entry of layout) {
      let key: number;
      if (isArray(entry[0])) {
        let array: number[] = entry[0] as number[];
        key = (array[0] << Number_8) + array[1];
        tables.opcodes.set(key,array);
      } else {
        key = entry[0] as number;
        tables.opcodes.set(key, [key]);
      }
      let string: string = entry[1] as string;
      tables.keyToNameMap.set(key,string);
      tables.nameToKeyMap.set(string,key);
      tables.types.set(key ,entry[Number_2] as number | number[]);
      tables.defaults.set(key,entry[Number_3] as number | number[]);

      tables.order.push(key);
    }

    return tables;
  }
}

class Stream {
  bytes: Uint8Array;
  start: number;
  pos: number;
  end: number;
  dict?: Dict | null;
  parameters?: Dict | null;
  constructor(arrayBuffer?: ArrayBuffer, start: number = null, length: number = null, dict: Dict = null) {
    this.bytes = new Uint8Array(arrayBuffer);
    this.start = start || 0;
    this.pos = this.start;
    if(start !== null && length !== null) {
      this.end = start + length;
    } else {
      this.end = this.bytes.length;
    }
    this.dict = dict??null;
  }
  get length(): number {
    return this.end - this.start;
  }
  getByte(): number | null {
    if (this.pos >= this.end) {
      return null;
    }
    let byte:number = this.bytes[this.pos]
    this.pos += 1;
    return byte;
  }

  getBytes(length: number = 0): Uint8Array {
    const pos: number = this.pos;
    const strEnd: number = this.end;
    if (length === 0) {
      return this.bytes.subarray(pos, strEnd);
    }
    let end:number = pos + length;
    if (end > strEnd) {
      end = strEnd;
    }
    this.pos = end;
    return this.bytes.subarray(pos, end);
  }

  lookChar(): string | null {
    if (this.pos >= this.end) {
      return null;
    }
    return String.fromCharCode(this.bytes[this.pos]);
  }

  getChar(): string | null {
    if (this.pos >= this.end) {
      return null;
    }
    let byte: number = this.bytes[this.pos];
    this.pos += 1;
    return String.fromCharCode(byte);
  }

  skip(n: number = null): void {
    this.pos += n ?  n : 1
  }
  reset(): void {
    this.pos = this.start;
  }

  moveStart(): void {
    this.start = this.pos;
  }

  makeSubStream(start: number, length: number = null, dict: Dict = null): Stream {
    return new Stream(this.bytes.buffer, start, length, dict);
  }
}

class DecodeStream extends Stream  {
  bufferLength: number = 0;
  eof: boolean = false;
  buffer: Uint8Array | null;

  constructor() {
    super((new Uint8Array()).buffer);
    this.bufferLength = 0;
    this.eof = false;
    this.buffer = null;
    this.pos = 0;
  }

  ensureBuffer(requested: number): Uint8Array | null {
    let buffer: Uint8Array = this.buffer;
    let current: number = buffer ? buffer.length : 0;
    if (requested < current){
      return buffer;
    }
    let size:number = 512;
    while (size < requested) {
      size <<= 1;
    }
    let buffer2 = new Uint8Array(size).fill(0);
    for (let i = 0; i < current; ++i) {
      buffer2[i] = buffer[i];
    }
    this.buffer = buffer2
    return buffer2;
  }

  getByte(): number | null {
    let pos:number = this.pos;
    while (this.bufferLength <= pos) {
      if (this.eof) {
        return null;
      }
      this.readBlock();
    }
    let byte:number = this.buffer[this.pos];
    if (byte !== 0) {
      this.pos += 1;
      return byte;
    } else {
      return null;
    }
  }

  getBytes(length: number): Uint8Array {
    let end: number;
    let pos:number = this.pos;

    if (length > 0) {
      this.ensureBuffer(pos + length);
      end = pos + length;

      while (!this.eof && this.bufferLength < end) {
        this.readBlock();
      }

      let bufEnd:number = this.bufferLength;
      if (end > bufEnd) {
        end = bufEnd;
      }
    } else {
      while (!this.eof) {
        this.readBlock();
      }
      end = this.bufferLength;
      if (end == 0) {
        this.buffer = new Uint8Array();
      }
    }
    this.pos = end;
    return this.buffer.subarray(pos, end);
  }

  lookChar(): string | null {
    let pos: number = this.pos;
    while (this.bufferLength <= pos) {
      if (this.eof) {
        return null;
      }
      this.readBlock();
    }
    let byte:number = this.buffer[this.pos];
    if (byte !== null && byte !== undefined){
      return String.fromCharCode(byte);
    } else {
      return null;
    }
  }

  getChar(): string | null {
    const pos:number = this.pos;
    while (this.bufferLength <= pos) {
      if (this.eof) {
        return null;
      }
      this.readBlock();
    }
    let byte:number = this.buffer[this.pos];
    if (byte !== undefined) {
      this.pos += 1
      return String.fromCharCode(byte);
    } else {
      return null;
    }
  }

  makeSubStream(start: number, length: number, dict: Dict): Stream {
    let end:number = start + length;
    while (this.bufferLength <= end && !this.eof) {
      this.readBlock();
    }
    return new Stream(this.buffer, start, length, dict);
  }

  skip(n: number): void {
    this.pos += n ? n : 1;
  }

  reset(): void {
    this.pos = 0;
  }

  readBlock(): void {
  }
}

class Transform {
  calculate(subbands: TransformResult[], u0: number, v0: number): TransformResult {
    let ll: TransformResult = subbands[0];
    for (let i = 1; i < subbands.length; i += Number_3) {
      ll = this.iterate(ll, subbands[i], subbands[i + 1], subbands[i + Number_2], u0, v0);
    }
    return ll;
  }

  iterate(ll: TransformResult, hl: TransformResult, lh: TransformResult, hh: TransformResult,
    u0: number, v0: number): TransformResult {
    const llWidth: number = ll.width;
    const llHeight: number = ll.height;
    const llItems:Uint8Array = ll.items;
    const hlWidth: number = hl.width;
    const hlHeight: number = hl.height;
    const hlItems: Uint8Array = hl.items;
    const lhWidth: number = lh.width;
    const lhHeight: number = lh.height;
    const lhItems: Uint8Array = lh.items;
    const hhWidth: number = hh.width;
    const hhHeight: number = hh.height;
    const hhItems: Uint8Array = hh.items;

    const width: number = llWidth + hlWidth;
    const height: number = llHeight + lhHeight;
    let items:Float32Array = new Float32Array(width * height).fill(0);

    for (let i = 0; i < llHeight; i++) {
      let k:number = i * llWidth;
      let l:number = i * Number_2 * width;
      for (let j = 0; j < llWidth; j++){
        items[l] = llItems[k];
        k += 1;
        l += Number_2;
      }
    }
    for (let i = 0; i < hlHeight; i++) {
      let k:number = i * hlWidth;
      let l: number = i * Number_2 * width + 1;
      for (let j = 0; j < hlWidth; j++){
        items[l] = hlItems[k];
        k += 1;
        l += Number_2;
      }
    }
    for (let i = 0; i < lhHeight; i++) {
      let k:number = i * lhWidth;
      let l:number = (i * Number_2 + 1) * width;
      for (let j = 0; j < lhWidth; j++, k++, l += Number_2){
        items[l] = lhItems[k];
        k += 1;
        l += Number_2;
      }
    }
    for (let i = 0; i < hhHeight; i++) {
      let k:number = i * hhWidth;
      let l:number = (i * Number_2 + 1) * width + 1;
      for (let j = 0; j < hhWidth; j++){
        items[l] = hhItems[k];
        k += 1;
        l += Number_2;
      }
    }
    let bufferPadding:number = Number_4;
    let bufferLength:Float32Array = new Float32Array(Math.max(width, height) + Number_2 * bufferPadding).fill(0);
    let buffer: Float32Array = new Float32Array(bufferLength).fill(0);
    let bufferOut: Float32Array = new Float32Array(bufferLength).fill(0);
    for (let v = 0; v < height; v++) {
      if (width == 1) {
        if ((u0 % 1.0) !== 0) {
          items[v * width] /= Number_2;
        }
        continue;
      }
      let k:number = v * width;
      let l:number = bufferPadding;
      for (let u: number = 0; u < width; u++){
        buffer[l] = items[k];
        k += 1;
        l += 1;
      }
      let i1:number = bufferPadding - 1;
      let j1:number = bufferPadding + 1;
      let i2:number = bufferPadding + width - Number_2;
      let j2:number = bufferPadding + width;
      buffer[i1] = buffer[j1];
      i1 -= 1;
      j1 += 1;

      buffer[j2] = buffer[i2];
      j2 += 1;
      i2 -= 1;

      buffer[i1] = buffer[j1];
      i1 -= 1;
      j1 += 1;

      buffer[j2] = buffer[i2];
      j2 += 1;
      i2 -= 1;

      buffer[i1] = buffer[j1];
      i1 -= 1;
      j1 += 1;

      buffer[j2] = buffer[i2];
      j2 += 1;
      i2 -= 1;

      buffer[i1] = buffer[j1];
      i1 -= 1;
      j1 += 1;

      buffer[j2] = buffer[i2];
      j2 += 1;
      i2 -= 1;

      this.filter(Array.from(buffer), bufferPadding, width, u0, Array.from(bufferOut));

      k = v * width;
      l = bufferPadding;
      for (let u = 0; u < width; u++){
        items[k] = bufferOut[l];
        k += 1;
        l += 1;
      }
    }
    for (let u = 0; u < width; u++) {
      if (height == 1) {
        if ((v0 % 1.0) !== 0) {
          items[u] /= Number_2;
        }
        continue;
      }
      let k:number = u;
      let l:number = bufferPadding;
      for (let v = 0; v < height; v++){
        buffer[l] = items[k];
        k += width;
        l += 1;
      }
      let i1:number = bufferPadding - 1;
      let j1:number = bufferPadding + 1;
      let i2:number = bufferPadding + height - Number_2;
      let j2:number = bufferPadding + height;
      buffer[i1] = buffer[j1];
      i1 -= 1;
      j1 += 1;

      buffer[j2] = buffer[i2];
      j2 += 1;
      i2 -= 1;

      buffer[i1] = buffer[j1];
      i1 -= 1;
      j1 += 1;

      buffer[j2] = buffer[i2];
      j2 += 1;
      i2 -= 1;

      buffer[i1] = buffer[j1];
      i1 -= 1;
      j1 += 1;

      buffer[j2] = buffer[i2];
      j2 += 1;
      i2 -= 1;

      buffer[i1] = buffer[j1];
      i1 -= 1;
      j1 += 1;

      buffer[j2] = buffer[i2];

      this.filter(Array.from(buffer), bufferPadding, height, v0, Array.from(bufferOut));
      k = u;
      l = bufferPadding;
      for (let v = 0; v < height; v++){
        items[k] = bufferOut[l];
        k += width;
        l += 1;
      }
    }
    return  {width: width, height: height, items: Uint8Array.from(items),};
  }

  filter(y: number[], offset: number, length: number, i0: number, x: number[]): void {
  }
}

class location {
  protocol: string = '';
}

class Console {
  log(s: string): void {
    print(s);
  }

  error(s: string): void {
    print(s);
  }
}

class Document {
  body: PDFElement = new PDFElement('body');
  documentElement: PDFElement = new PDFElement('document');
  createElement(element_type: string): PDFElement {
    let element: PDFElement;
    if (element_type === 'canvas') {
      element = new Canvas();
    } else {
      element = new PDFElement(element_type);
    }
    element.parentNode = new PDFElement('dummy_parent');
    return element;
  }

  getElementById(name: string): PDFElement {
    if (name === 'canvas') {
      return new Canvas();
    } else {
      return null;
    }
  }

  getElementsByTagName(element: string): PDFElement[] | null {
    if (element === 'script') {
      return new Array(this.createElement(element));
    }
    return null;
  }

  createEvent(name: string): PDFEvent {
    return new PDFEvent();
  }
}

class PDFEvent {
  name: string = '';
  initEvent(name: string,listener: boolean = false, flg:boolean = false): void {
    this.name = name;
  }
}

class Worker extends FakeWorker {
  url: string;
  constructor(url?: string) {
    super();
    this.url = url;
  }
}

class XMLHttpRequest {
  public url: string = '';
  public response: Uint8Array | null;
  public readyState: number = 0;
  public status: number = 0;
  onreadystatechange: () => void = () => {};
  public mozResponseType: string = '';
  public responseType: string = '';
  public expected: number = 0;
  public onerror:  (e: number) => void | null;
  public mozResponseArrayBuffer: Uint8Array | null;
  public responseArrayBuffer: Uint8Array | null;
  public mozResponse: Uint8Array | null;
  onload: () => void;
  onprogress: Function;
  open(type: string, url: string, some_bool: boolean = false): void {
    this.url = url;
  }
  overrideMimeType(): void {
  }
  send(data?:Uint8Array): void {
    this.response = PdfJS_window.__resources__[this.url];
    this.readyState = Number_4;
    this.status = 0;
    this.onreadystatechange();
  }
}

class Canvas extends PDFElement {
  width: number =NumberFloat_100;
  height: number =NumberFloat_100;
  style: Map<string, string> = new Map([["visibility", 'visibile' ]]);

  getContext(type: string = ''): PDFContext {
    return new PDFContext();
  }
}

class PDFPromise {
  name: string;
  isResolved: boolean;
  isRejected: boolean;
  errorString: string;
  _data: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Record<string, string>;
  hasData: boolean = false;
  callbacks: Function[];
  errbacks: ((str?:string) => void)[];
  progressbacks: Function[];
  onDataCallback: Function;
  public static EMPTY_PROMISE: Record<string, string> | null = null;

  constructor(name?: string, data: Record<string, string> = null) {
    this.name = name;
    this.isResolved = false;
    this.isRejected = false;
    this.errorString = null;
    if (data !== null && data !== undefined){
      this.isResolved = true;
      this._data = data;
      this.hasData = true;
    } else {
      this.isResolved = false;
      this._data = PDFPromise.EMPTY_PROMISE;
    }

    this.callbacks = new Array<Function>();
    this.errbacks = new Array<(str?:string) => void>();
    this.progressbacks = new Array<Function>();
  }

  public static all(promises: PDFPromise[]): PDFPromise {
    const deferred:PDFPromise = new PDFPromise();
    let unresolved:number = promises.length;
    let results: Record<string, string> | Record<string, string>[] = new Array<Record<string, string>>();
    if (unresolved === 0) {
      deferred.resolve(results);
      return deferred;
    }
    for (let i = 0; i < promises.length; ++i) {
      let promise:PDFPromise = promises[i];
      promise.then((value: Record<string, string>) => {
        results[i] = value;
        unresolved -= 1;
        if (unresolved === 0){
          deferred.resolve(results);
        }
      });
    }
    return deferred;
  }

  public set data(value: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name) {
    if (value == null ){
      return;
    }

    if (this._data !== null && this._data !== undefined){
      error(`Promise ${this.name}: Cannot set the data of a promise twice`);
    }
    this._data = value;
    this.hasData = true;

    if (this.onDataCallback !== null && this.onDataCallback !== undefined) {
      this.onDataCallback(value);
    }
  }

  public get data(): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    if (this._data == null) {
      error(`Promise ${this.name}: Cannot get data that isn't set`)
    }
    return this._data;
  }

  public onData(callback: Function): void {
    if (this._data !== null) {
      callback(this._data);
    } else {
      this.onDataCallback = callback;
    }
  }

  public resolve(data:object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = null): void {
    if (this.isResolved) {
      error(`A Promise can be resolved only once ${this.name}`);
    }
    if (this.isRejected) {
      error(`The Promise was already rejected ${this.name}`);
    }

    this.isResolved = true;
    this.data = data;
    const callbacks:Function[] = this.callbacks;

    for (let i = 0; i < callbacks.length; i++) {
      callbacks[i](data);
    }
  }

  public progress(data: Record<string, string>): void {
    const callbacks:Function[] = this.progressbacks;
    for (let i: number = 0; i < callbacks.length; i++) {
      callbacks[i](data);
    }
  }

  public reject(reason: string = null): void {
    if (this.isRejected) {
      error(`A Promise can be rejected only once ${this.name}`);
    }

    if (this.isResolved) {
      error(`The Promise was already resolved ${this.name}`);
    }

    this.isRejected = true;
    this.errorString = reason;
    const errbacks: ((str?:string) => void) [] = this.errbacks;

    for (let i = 0; i < errbacks.length; i++) {
      errbacks[i](reason);
    }
  }

  public then(callback: Function, errback: (str?:string) => void = null, progressback: Function = null): void {
    if (callback === null || callback === undefined){
      error(`Requiring callback ${this.name}`);
    }
    if (this.isResolved) {
      let data:object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = this.data;
      callback(data);
    } else if (this.isRejected && errback !== null) {
      let error: string = this.errorString;
      errback(error);
    } else {
      this.callbacks.push(callback);
      if (errback !== null){
        this.errbacks.push(errback);
      }
    }

    if (progressback !== null){
      this.progressbacks.push(progressback);
    }
  }
}

class PDFJSClass {
  build?: string = '3cc61f0';
  pdfBug?: boolean = false;
  disableWorker: boolean = true;
  enableStats?: boolean = false;
  workerSrc?: string = '';
  isFirefoxExtension?: boolean = false;

  getPdf(url: string, progress: (loaded: number, total: number) => void, error: (e: number) => void,
    callback: (data:object|string|number) => void): void {
    let xhr: XMLHttpRequest = PdfJS_window.xMLHttpRequest;
    xhr.open('GET', url);
    xhr.mozResponseType = 'arraybuffer';
    xhr.responseType = 'arraybuffer';
    let protocolPrefix: string = url;
    xhr.expected = (protocolPrefix.includes('http:') || protocolPrefix.includes('https:')) ? Number_200 : 0
    xhr.onprogress = progress;
    xhr.onerror = error;

    xhr.onreadystatechange = ():void => {
      if (xhr.readyState === Number_4) {
        if (xhr.status === xhr.expected) {
          let data = xhr.mozResponseArrayBuffer ?? xhr.mozResponse ?? xhr.responseArrayBuffer ?? xhr.response;
          callback(data);
        } else {
          error(xhr.status);
        }
      }
    };
    xhr.send(null);
  }

  getDocument(source: string | Uint8Array): PDFPromise {
    const promise: PDFPromise = this.Promise();
    const transport:WorkerTransport = new WorkerTransport(promise);
    if (typeof source === 'string') {
      PDFJS.getPdf!(
        source,
        (evtLoaded, evtTotal) => {
          if (evtTotal > 0) {
            promise.progress({ 'loaded': String(evtLoaded), 'total': String(evtTotal) });
          }
        }, (e) => {
          promise.reject(`Unexpected server response of ${e}.`);
        }, (data: Uint8Array) => {
          transport.sendData(data);
        });
    } else {
      transport.sendData(source);
    }
    return promise;
  }

  Promise(): PDFPromise {
    return new PDFPromise();
  }
}

const PDFJS: PDFJSClass = new PDFJSClass();

class PDFWindow {
  window?: PDFWindow;
  __timeouts__: (Function | string) [] = new Array();
  __resources__: Record<string, Uint8Array> | string = {};

  __to64__: string[] = [
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '='
  ];
  __from64__: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
  location:location = new location();
  Worker?: Worker;
  PDFJS?: PDFJSClass;
  _document?: Document;
  console: Console = new Console();
  xMLHttpRequest: XMLHttpRequest = new XMLHttpRequest();
  constructor() {
    this.window = this;
  }

  setTimeout(cmd: Function, delay: number = 0):void {
    this.__timeouts__.push(cmd);
  }

  flushTimeouts():void {
    while (PdfJS_window.__timeouts__.length !== 0) {
      let next: string | Function = PdfJS_window.__timeouts__.pop();
      if (next as ()=>void) {
        (next as ()=>void)();
      } else if (next as (e:number) => void) {
        (next as (e:number) => void)(0);
      }
    }
  }

  btoa(data: string): string {
    let result: string = '';
    if (data.length > 0) {
      let i: number = 0;
      while (i < data.length) {
        let b1: number = data.charCodeAt(i) & Number_0xff;
        let b2: number = data.charCodeAt(i + 1 < data.length ? i + 1 : 0) & Number_0xff;
        let b3: number = data.charCodeAt(i +Number_2 < data.length ? i +Number_2 : 0) & Number_0xff;
        i += Number_3;
        result += PdfJS_window.__to64__[b1 >>Number_2];
        if (i === data.length +Number_2) {
          result += PdfJS_window.__to64__[(b1 & Number_0x3) << Number_4] + '==';
        } else {
          result += PdfJS_window.__to64__[((b1 & Number_0x3) << Number_4) | (b2 >> Number_4)];
          if (i === data.length + 1) {
            result += PdfJS_window.__to64__[(b2 & Number_0xF) <<Number_2] + '=';
          } else {
            result += PdfJS_window.__to64__[((b2 & Number_0xF) <<Number_2) | (b3 >> Number_6)] +
            PdfJS_window.__to64__[b3 & Number_0x3F];
          }
        }
      }
    }
    return result;
  }

  atob(data: string): string {
    let base64Index = (char: number): number => {
      let base64Chars: number[] = Array.from(PdfJS_window.__from64__, c => c.charCodeAt(0));
      for (let i = 0; i < base64Chars.length; i++) {
        if (base64Chars[i] == char) {
          return i;
        }
      }
      return 0;
    };

    let result: string = "";
    let i: number = 0;
    let encodedData: number[] = Array.from(data, c => c.charCodeAt(0));
    while (i < encodedData.length) {
      const x1: number = base64Index(encodedData[i]);
      const x2: number = base64Index(encodedData[i + 1]);
      const x3: number = base64Index(encodedData[i +Number_2]);
      const x4: number = base64Index(encodedData[i + Number_3]);
      result += String.fromCharCode((x1 <<Number_2) | (x2 >> Number_4));
      if (x3 !== Number_0x40) {
        result += String.fromCharCode(((x2 & Number_0x0F) << Number_4) | (x3 >> Number_2));
        if (x4 !== Number_0x40) {
          result += String.fromCharCode(((x3 & Number_0x03) << Number_6) | x4);
        }
      }
      i += Number_4;
    }
    return result;
  }

  addEventListener(name: string, listener: Function = null, flg: boolean = false): void {
    this.setTimeout(listener);
  }

  get document():Document {
    if (this._document === null || this._document === undefined) {
      this._document = new Document()
    }
    return this._document
  }

}
let PdfJS_window: PDFWindow = new PDFWindow();

function PdfJS_windowInstall(name: string, x:object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name) {
}

function getPDF(): string {
  return "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NyAzOTkuNyA1MTMuOSA3OTkuNCAyODUuNSAzNDIuNiAyODUuNSA1MTMuOSA1MTMuOSA1MTMuOSA1MTMuOSA1MTMuOSA1MTMuOSA1MTMuOSA1MTMuOSA1MTMuOSA1MTMuOSA1MTMuOSAyODUuNSAyODUuNSAyODUuNSA3OTkuNCA0ODUuMyA0ODUuMyA3OTkuNCA3NzAuNyA3MjcuOSA3NDIuMyA3ODUgNjk5LjQgNjcwLjggODA2LjUgNzcwLjcgMzcxIDUyOC4xIDc5OS4yIDY0Mi4zIDk0MiA3NzAuNyA3OTkuNCA2OTkuNCA3OTkuNCA3NTYuNSA1NzEgNzQyLjMgNzcwLjcgNzcwLjcgMTA1Ni4yIDc3MC43IDc3MC43IDYyOC4xIDI4NS41IDUxMy45IDI4NS41IDUxMy45IDI4NS41IDI4NS41IDUxMy45IDU3MSA0NTYuOCA1NzEgNDU3LjIgMzE0IDUxMy45IDU3MSAyODUuNSAzMTQgNTQyLjQgMjg1LjUgODU2LjUgNTcxIDUxMy45IDU3MSA1NDIuNCA0MDIgNDA1LjQgMzk5LjcgNTcxIDU0Mi40IDc0Mi4zIDU0Mi40IDU0Mi40XQplbmRvYmoKMTYgMCBvYmoKWzYzOC45XQplbmRvYmoKMTcgMCBvYmoKWzU2Mi41IDU2Mi41IDU2Mi41IDU2Mi41IDU2Mi41IDU2Mi41IDU2Mi41IDU2Mi41IDU2Mi41IDMxMi41IDMxMi41IDM0Mi42IDg3NSA1MzEuMiA1MzEuMiA4NzUgODQ5LjUgNzk5LjggODEyLjUgODYyLjMgNzM4LjQgNzA3LjIgODg0LjMgODc5LjYgNDE5IDU4MSA4ODAuOCA2NzUuOSAxMDY3LjEgODc5LjYgODQ0LjkgNzY4LjUgODQ0LjkgODM5LjEgNjI1IDc4Mi40IDg2NC42IDg0OS41IDExNjIgODQ5LjUgODQ5LjUgNjg3LjUgMzEyLjUgNTgxIDMxMi41IDU2Mi41IDMxMi41IDMxMi41IDU0Ni45IDYyNSA1MDAgNjI1IDUxMy4zIDM0My43IDU2Mi41IDYyNSAzMTIuNSAzNDMuNyA1OTMuNyAzMTIuNSA5MzcuNSA2MjUgNTYyLjUgNjI1IDU5My43IDQ1OS41IDQ0My44IDQzNy41IDYyNV0KZW5kb2JqCjE4IDAgb2JqCls1ODMuMyA1NTUuNiA1NTUuNiA4MzMuMyA4MzMuMyAyNzcuOCAzMDUuNiA1MDAgNTAwIDUwMCA1MDAgNTAwIDc1MCA0NDQuNCA1MDAgNzIyLjIgNzc3LjggNTAwIDkwMi44IDEwMTMuOSA3NzcuOCAyNzcuOCAyNzcuOCA1MDAgODMzLjMgNTAwIDgzMy4zIDc3Ny44IDI3Ny44IDM4OC45IDM4OC45IDUwMCA3NzcuOCAyNzcuOCAzMzMuMyAyNzcuOCA1MDAgNTAwIDUwMCA1MDAgNTAwIDUwMCA1MDAgNTAwIDUwMCA1MDAgNTAwIDI3Ny44IDI3Ny44IDI3Ny44IDc3Ny44IDQ3Mi4yIDQ3Mi4yIDc3Ny44IDc1MCA3MDguMyA3MjIuMiA3NjMuOSA2ODAuNiA2NTIuOCA3ODQuNyA3NTAgMzYxLjEgNTEzLjkgNzc3LjggNjI1IDkxNi43IDc1MCA3NzcuOCA2ODAuNiA3NzcuOCA3MzYuMSA1NTUuNiA3MjIuMiA3NTAgNzUwIDEwMjcuOCA3NTAgNzUwIDYxMS4xIDI3Ny44IDUwMCAyNzcuOCA1MDAgMjc3LjggMjc3LjggNTAwIDU1NS42IDQ0NC40IDU1NS42IDQ0NC40IDMwNS42IDUwMCA1NTUuNiAyNzcuOCAzMDUuNiA1MjcuOCAyNzcuOCA4MzMuMyA1NTUuNiA1MDAgNTU1LjYgNTI3LjggMzkxLjcgMzk0LjQgMzg4LjkgNTU1LjYgNTI3LjggNzIyLjIgNTI3LjggNTI3LjggNDQ0LjRdCmVuZG9iagoxOSAwIG9iagpbODY5LjQgODE4LjEgODMwLjYgODgxLjkgNzU1LjYgNzIzLjYgOTA0LjIgOTAwIDQzNi4xIDU5NC40IDkwMS40IDY5MS43IDEwOTEuNyA5MDAgODYzLjkgNzg2LjEgODYzLjkgODYyLjUgNjM4LjkgODAwIDg4NC43IDg2OS40IDExODguOSA4NjkuNCA4NjkuNCA3MDIuOCAzMTkuNCA2MDIuOCAzMTkuNCA1NzUgMzE5LjQgMzE5LjQgNTU5IDYzOC45IDUxMS4xIDYzOC45IDUyNy4xIDM1MS40IDU3NSA2MzguOSAzMTkuNCAzNTEuNCA2MDYuOSAzMTkuNCA5NTguMyA2MzguOSA1NzUgNjM4LjkgNjA2LjkgNDczLjYgNDUzLjYgNDQ3LjJdCmVuZG9iagoyMCAwIG9iagpbMjcyIDMyNi40IDI3MiA0ODkuNiA0ODkuNiA0ODkuNiA0ODkuNiA0ODkuNiA0ODkuNiA0ODkuNiA0ODkuNiA0ODkuNiA0ODkuNiA0ODkuNiAyNzIgMjcyIDI3MiA3NjEuNiA0NjIuNCA0NjIuNCA3NjEuNiA3MzQgNjkzLjQgNzA3LjIgNzQ3LjggNjY2LjIgNjM5IDc2OC4zIDczNCAzNTMuMiA1MDMgNzYxLjIgNjExLjggODk3LjIgNzM0IDc2MS42IDY2Ni4yIDc2MS42IDcyMC42IDU0NCA3MDcuMiA3MzQgNzM0IDEwMDYgNzM0IDczNCA1OTguNCAyNzIgNDg5LjYgMjcyIDQ4OS42IDI3MiAyNzIgNDg5LjYgNTQ0IDQzNS4yIDU0NCA0MzUuMiAyOTkuMiA0ODkuNiA1NDQgMjcyIDI5OS4yIDUxNi44IDI3MiA4MTYgNTQ0IDQ4OS42IDU0NCA1MTYuOCAzODAuOCAzODYuMiAzODAuOCA1NDQgNTE2LjggNzA3LjIgNTE2LjggNTE2LjggNDM1LjIgNDg5LjYgOTc5LjIgNDg5LjYgNDg5LjYgNDg5LjZdCmVuZG9iagoyMSAwIG9iagpbNTAwXQplbmRvYmoKMjIgMCBvYmoKWzYwMi4xIDcyNi4zIDY5My4zIDMyNy42IDQ3MS41IDcxOS40IDU3NiA4NTAgNjkzLjMgNzE5LjggNjI4LjIgNzE5LjggNjgwLjUgNTEwLjkgNjY3LjYgNjkzLjMgNjkzLjMgOTU0LjUgNjkzLjMgNjkzLjMgNTYzLjEgMjQ5LjYgNDU4LjYgMjQ5LjYgNDU4LjYgMjQ5LjYgMjQ5LjYgNDU4LjYgNTEwLjkgNDA2LjQgNTEwLjkgNDA2LjQgMjc1LjggNDU4LjYgNTEwLjkgMjQ5LjYgMjc1LjggNDg0LjcgMjQ5LjYgNzcyLjEgNTEwLjkgNDU4LjYgNTEwLjkgNDg0LjcgMzU0LjEgMzU5LjQgMzU0LjEgNTEwLjkgNDg0LjcgNjY3LjYgNDg0LjcgNDg0LjddCmVuZG9iagoyMyAwIG9iaiA8PAovTGVuZ3RoMSAxNDc4Ci9MZW5ndGgyIDg1MTgKL0xlbmd0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v8GGxC7XToegrvebyXdbtw9p5HgLpTWptCKqTLk+yf8P4ygJngplbmRzdHJlYW0KZW5kb2JqCjQwIDAgb2JqIDw8Ci9UeXBlIC9Gb250RGVzY3JpcHRvcgovRm9udE5hbWUgL01GTURBRitDTVRUOQovRmxhZ3MgNAovRm9udEJCb3ggWy02IC0yMzMgNTQyIDY5OF0KL0FzY2VudCA2MTEKL0NhcEhlaWdodCA2MTEKL0Rlc2NlbnQgLTIyMgovSXRhbGljQW5nbGUgMAovU3RlbVYgNzQKL1hIZWlnaHQgNDMxCi9DaGFyU2V0ICgvZi9uL28vcikKL0ZvbnRGaWxlIDM5IDAgUgo+PiBlbmRvYmoKNyAwIG9iaiA8PAovVHlwZSAvRm9udAovU3VidHlwZSAvVHlwZTEKL0Jhc2VGb250IC9RWktSRE0rQ01CWDEwCi9Gb250RGVzY3JpcHRvciAyNCAwIFIKL0ZpcnN0Q2hhciA2NQovTGFzdENoYXIgMTE2Ci9XaWR0aHMgMTkgMCBSCj4+IGVuZG9iago5IDAgb2JqIDw8Ci9UeXBlIC9Gb250Ci9TdWJ0eXBlIC9UeXBlMQovQmFzZUZvbnQgL0dYR0dCUStDTUJYMTIKL0ZvbnREZXNjcmlwdG9yIDI2IDAgUgovRmlyc3RDaGFyIDQ5Ci9MYXN0Q2hhciAxMTcKL1dpZHRocyAxNyAwIFIKPj4gZW5kb2JqCjggMCBvYmogPDwKL1R5cGUgL0ZvbnQKL1N1YnR5cGUgL1R5cGUxCi9CYXNlRm9udCAvUlpQTUxTK0NNUjEwCi9Gb250RGVzY3JpcHRvciAyOCAwIFIKL0ZpcnN0Q2hhciAxMQovTGFzdENoYXIgMTIyCi9XaWR0aHMgMTggMCBSCj4+IGVuZG9iago2IDAgb2JqIDw8Ci9UeXBlIC9Gb250Ci9TdWJ0eXBlIC9UeXBlMQovQmFzZUZvbnQgL1BIQlhEUStDTVIxMgovRm9udERlc2NyaXB0b3IgMzAgMCBSCi9GaXJzdENoYXIgNDQKL0xhc3RDaGFyIDEyNwovV2lkdGhzIDIwIDAgUgo+PiBlbmRvYmoKNCAwIG9iaiA8PAovVHlwZSAvRm9udAovU3VidHlwZSAvVHlwZTEKL0Jhc2VGb250IC9KVFlNS04rQ01SMTcKL0ZvbnREZXNjcmlwdG9yIDMyIDAgUgovRmlyc3RDaGFyIDcwCi9MYXN0Q2hhciAxMjEKL1dpZHRocyAyMiAwIFIKPj4gZW5kb2JqCjExIDAgb2JqIDw8Ci9UeXBlIC9Gb250Ci9TdWJ0eXBlIC9UeXBlMQovQmFzZUZvbnQgL1RLSkhISStDTVI5Ci9Gb250RGVzY3JpcHRvciAzNCAwIFIKL0ZpcnN0Q2hhciAzMwovTGFzdENoYXIgMTIxCi9XaWR0aHMgMTUgMCBSCj4+IGVuZG9iago1IDAgb2JqIDw8Ci9UeXBlIC9Gb250Ci9TdWJ0eXBlIC9UeXBlMQovQmFzZUZvbnQgL1VFSVpZVytDTVNZMTAKL0ZvbnREZXNjcmlwdG9yIDM2IDAgUgovRmlyc3RDaGFyIDMKL0xhc3RDaGFyIDMKL1dpZHRocyAyMSAwIFIKPj4gZW5kb2JqCjEwIDAgb2JqIDw8Ci9UeXBlIC9Gb250Ci9TdWJ0eXBlIC9UeXBlMQovQmFzZUZvbnQgL0dVT1dUSytDTVNZNgovRm9udERlc2NyaXB0b3IgMzggMCBSCi9GaXJzdENoYXIgMwovTGFzdENoYXIgMwovV2lkdGhzIDE2IDAgUgo+PiBlbmRvYmoKMTIgMCBvYmogPDwKL1R5cGUgL0ZvbnQKL1N1YnR5cGUgL1R5cGUxCi9CYXNlRm9udCAvTUZNREFGK0NNVFQ5Ci9Gb250RGVzY3JpcHRvciA0MCAwIFIKL0ZpcnN0Q2hhciAxMDIKL0xhc3RDaGFyIDExNAovV2lkdGhzIDE0IDAgUgo+PiBlbmRvYmoKMTMgMCBvYmogPDwKL1R5cGUgL1BhZ2VzCi9Db3VudCAxCi9LaWRzIFsyIDAgUl0KPj4gZW5kb2JqCjQxIDAgb2JqIDw8Ci9UeXBlIC9DYXRhbG9nCi9QYWdlcyAxMyAwIFIKPj4gZW5kb2JqCjQyIDAgb2JqIDw8Ci9Qcm9kdWNlciAocGRmVGVYLTEuNDAuMTApCi9DcmVhdG9yIChUZVgpCi9DcmVhdGlvbkRhdGUgKEQ6MjAxMjA2MTExODA4NDYrMDInMDAnKQovTW9kRGF0ZSAoRDoyMDEyMDYxMTE4MDg0NiswMicwMCcpCi9UcmFwcGVkIC9GYWxzZQovUFRFWC5GdWxsYmFubmVyIChUaGlzIGlzIHBkZlRlWCwgVmVyc2lvbiAzLjE0MTU5MjYtMS40MC4xMC0yLjIgKFRlWCBMaXZlIDIwMDkvRGViaWFuKSBrcGF0aHNlYSB2ZXJzaW9uIDUuMC4wKQo+PiBlbmRvYmoKeHJlZgowIDQzCjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMzMxNiAwMDAwMCBuIAowMDAwMDAzMjA0IDAwMDAwIG4gCjAwMDAwMDAwMTUgMDAwMDAgbiAKMDAwMDEwODgzNCAwMDAwMCBuIAowMDAwMTA5MTEyIDAwMDAwIG4gCjAwMDAxMDg2OTUgMDAwMDAgbiAKMDAwMDEwODI3NiAwMDAwMCBuIAowMDAwMTA4NTU2IDAwMDAwIG4gCjAwMDAxMDg0MTYgMDAwMDAgbiAKMDAwMDEwOTI0OSAwMDAwMCBuIAowMDAwMTA4OTczIDAwMDAwIG4gCjAwMDAxMDkzODYgMDAwMDAgbiAKMDAwMDEwOTUyNyAwMDAwMCBuIAowMDAwMDAzNDc0IDAwMDAwIG4gCjAwMDAwMDM1NDQgMDAwMDAgbiAKMDAwMDAwNDA3MSAwMDAwMCBuIAowMDAwMDA0MDk1IDAwMDAwIG4gCjAwMDAwMDQ1MDEgMDAwMDAgbiAKMDAwMDAwNTEyMyAwMDAwMCBuIAowMDAwMDA1NDQxIDAwMDAwIG4gCjAwMDAwMDU5MDYgMDAwMDAgbiAKMDAwMDAwNTkyOCAwMDAwMCBuIAowMDAwMDA2MjU0IDAwMDAwIG4gCjAwMDAwMTU4NzcgMDAwMDAgbiAKMDAwMDAxNjEwOCAwMDAwMCBuIAowMDAwMDI0NDIxIDAwMDAwIG4gCjAwMDAwMjQ2NjAgMDAwMDAgbiAKMDAwMDA0MzkyNSAwMDAwMCBuIAowMDAwMDQ0MzQyIDAwMDAwIG4gCjAwMDAwNTg3MzMgMDAwMDAgbiAKMDAwMDA1OTA1NiAwMDAwMCBuIAowMDAwMDY5NjY1IDAwMDAwIG4gCjAwMDAwNjk5MTcgMDAwMDAgbiAKMDAwMDA4NTU4MCAwMDAwMCBuIAowMDAwMDg1OTMxIDAwMDAwIG4gCjAwMDAwOTMwNDggMDAwMDAgbiAKMDAwMDA5MzI3OSAwMDAwMCBuIAowMDAwMTAwNDQ1IDAwMDAwIG4gCjAwMDAxMDA2NzQgMDAwMDAgbiAKMDAwMDEwODA1NSAwMDAwMCBuIAowMDAwMTA5NTg1IDAwMDAwIG4gCjAwMDAxMDk2MzYgMDAwMDAgbiAKdHJhaWxlcgo8PCAvU2l6ZSA0MwovUm9vdCA0MSAwIFIKL0luZm8gNDIgMCBSCi9JRCBbPEE1MTIyOERDMEI2RDhERTcwNDA5QjM4RUY1MDJDQkYwPiA8QTUxMjI4REMwQjZEOERFNzA0MDlCMzhFRjUwMkNCRjA+XSA+PgpzdGFydHhyZWYKMTA5OTAyCiUlRU9GCg=="
}

let globalScope: PDFWindow = PdfJS_window.window;
const isWorker:boolean = (typeof PdfJS_window.window === 'undefined');

const ERRORS:number = 0;
const WARNINGS:number = 1;
const TODOS:number = Number_5;
const verbosity:number = 1;

if (globalScope.PDFJS === null || typeof globalScope.PDFJS === "undefined") {
  globalScope.PDFJS = new PDFJSClass();
}

globalScope.PDFJS.pdfBug = false;

class Page {
  pe: PartialEvaluator
  pageNumber: number;
  pageDict: Dict | null;
  xref: XRef;
  ref: Ref;

  displayReadyPromise?: PDFPromise | null;

  constructor(xref: XRef, pageNumber: number, pageDict: Dict, ref: Ref) {
    this.pageNumber = pageNumber;
    this.pageDict = pageDict;
    this.xref = xref;
    this.ref = ref;

    this.displayReadyPromise = null;
  }

  getPageProp(key: string): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name  {
    return this.pageDict.get(key);
  }

  inheritPageProp(key: string): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    let dict: Dict = this.pageDict;
    let obj = dict.get(key);
    while (obj === null) {
      dict = dict.get('Parent') as Dict;
      if (dict === null) {
        break;
      }
      obj = dict.get(key);
    }
    return obj;
  }

  get content(): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    return this.getPageProp("Contents");
  }

  get resources(): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    return this.getPageProp("Resources");
  }

  get mediaBox(): number[] {
    let obj: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name= this.inheritPageProp('MediaBox');
    if (!isArray(obj) || (obj as number[]).length !== Number_4) {
      obj = [0, 0, 612, 792];
    }
    return obj as number[];
  }

  get view(): number[] {
    let mediaBox:number[] = this.mediaBox;
    let cropBox: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = this.inheritPageProp('CropBox');
    if (!isArray(cropBox) || (cropBox as number[]).length !== Number_4) {
      return mediaBox;
    }
    cropBox = Util.intersect(cropBox as number[], mediaBox) as number[];
    if (cropBox === null || cropBox === undefined) {
      return mediaBox;
    }
    return cropBox as number[];
  }

  get annotations() {
    return this.inheritPageProp('Annots');
  }

  get rotate(): number {
    let rotate: number = this.inheritPageProp('Rotate') as number || 0;
    if (rotate % Number_90 !== 0) {
      rotate = 0;
    } else if (rotate >= Number_360) {
      rotate = rotate % Number_360;
    } else if (rotate < 0) {
      rotate = ((rotate % Number_360) + Number_360) % Number_360;
    }
    return rotate;
  }

  getOperatorList(handler: MessageHandler, dependency: string[]):OperatorList {
    let xref:XRef = this.xref;
    let content = this.content;
    let resources:Dict = this.resources as Dict;
    if (isArray(content)) {
      let streams: Stream[] = new Array<Stream>();
      let n: number = (content as []).length
      for (let i = 0; i < n; ++i) {
        streams.push(xref.fetchIfRef(content[i]) as Stream);
      }
      content = new StreamsSequenceStream(streams);
    } else if (isStream(content)) {
      (content as Stream).reset();
    } else if (!content) {
      content = new Stream(new Uint8Array());
    }
    this.pe = new PartialEvaluator(xref, handler, `p${this.pageNumber}_`);
    let pe:PartialEvaluator = this.pe;
    return pe.getOperatorList(content as Stream, resources, dependency);
  }

  getLinks():PageAnnotationItem[] {
    let links: PageAnnotationItem[] = new Array<PageAnnotationItem>();
    let annotations: PageAnnotationItem[] = this.getAnnotations();
    for (let i = 0; i < annotations.length; ++i) {
      if (annotations[i].type !== 'Link'){
        continue;
      }
      links.push(annotations[i]);
    }
    return links;
  }

  getAnnotations(): PageAnnotationItem[] {
    let xref:XRef = this.xref;
    let getInheritableProperty = (annotation: Dict, name: string): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name => {
      let item:Dict = annotation;
      while (item && !item.has(name)) {
        item = item.get('Parent') as Dict;
      }
      if (item === null || item === undefined) {
        return null;
      }
      return item.get(name);
    };
    let isValidUrl = (url: string):boolean => {
      if (url === null || url === undefined) {
        return false;
      }
      let colon:number = url.indexOf(':');
      if (colon < 0) {
        return false;
      }
      let protocolStr:string = url.substring(0, colon);
      switch (protocolStr) {
        case 'http':
        case 'https':
        case 'ftp':
        case 'mailto':
          return true;
        default:
          return false;
      }
    }

    let annotations: Ref[] = this.annotations as Ref[] ?? new Array<Ref>();
    let items: PageAnnotationItem[] = new Array<PageAnnotationItem>();
    for (let i = 0; i < annotations.length; ++i) {
      let annotationRef:Ref = annotations[i];
      let annotation:Dict = xref.fetch(annotationRef) as Dict;
      if (!isDict(annotation)) {
        continue;
      }
      let subtype:Name = annotation.get('Subtype') as Name;
      if (!isName(subtype)) {
        continue;
      }
      let rect:string = annotation.get('Rect') as string;

      let item: PageAnnotationItem = new PageAnnotationItem();
      item.type = subtype.name;
      item.rect = rect;
      switch (subtype.name) {
        case 'Link':
          let a:Dict = annotation.get('A') as Dict;
          if (a !== null) {
            switch ((a.get('S') as Name).name) {
              case 'URI':{
                let url:string = a.get('URI') as string;
                if (!isValidUrl(url)) {
                  url = '';
                }
                item.url = url;
              }
                break;
              case 'GoTo':
                item.dest = a.get('D') as string;
                break;
              default:
                TODO('other link types');
                break;
            }
          } else if (annotation.has('Dest')) {
            let dest: Name | string = annotation.get('Dest') as Name | string;
            item.dest = isName(dest) ? (dest as Name).name : dest as string;
          }
          break;
        case 'Widget': {
          let fieldType:Name = getInheritableProperty(annotation, 'FT') as Name;
          if (!isName(fieldType)) {
            break;
          }
          item.fieldType = fieldType.name;
          let fieldName: string[] = new Array<string>();
          let namedItem:Dict = annotation;
          let ref:Ref = annotationRef;
          while (namedItem !== null) {
            let parent: Dict = namedItem.get('Parent') as Dict;
            let parentRef: Ref = namedItem.getRaw('Parent') as Ref;
            let name: string = namedItem.get('T') as string;
            if (name !== undefined && name !== null) {
              fieldName.unshift(stringToPDFString(name));
            } else {
              let kids:Ref[] = parent.get('Kids') as Ref[];
              let j: number = 0;
              let jj:number = kids.length;
              while (j < jj) {
                let kidRef:Ref = kids[j];
                if (kidRef.num === ref.num || kidRef.gen === ref.gen) {
                  break;
                }
                j += 1;
              }
              fieldName.unshift('`' + String(j));
            }
            namedItem = parent;
            ref = parentRef;
          }
          item.fullName = fieldName.join('.');
          let alternativeText:string = stringToPDFString(annotation.get('TU') as string || '') as string;
          item.alternativeText = alternativeText;
          let da:string = getInheritableProperty(annotation, 'DA') as string || '';
          let regex: RegExp = RegExp('([\d\.]+)\sTf');
          let match:RegExpExecArray = regex.exec(da);
          if (match !== undefined && match !== null) {
            item.fontSize = Number.parseFloat(match[0]);
          }
          item.textAlignment = String(getInheritableProperty(annotation, 'Q'));
          item.flags = getInheritableProperty(annotation, 'Ff') as number || 0;
        }
          break;
        case 'Text':
          item.content = stringToPDFString(annotation.get('Contents') as string || '');
          item.title = stringToPDFString(annotation.get('T') as string || '');
          item.name = !annotation.has('Name') ? 'Note' : (annotation.get('Name') as Name).name || "";
          break;
        default:
          TODO('unimplemented annotation type: ' + subtype.name);
          break;
      }
      items.push(item);
    }
    return items;
  }
}

class PDFDocument {
  stream: Stream;
  xref: XRef;
  acroForm: Dict;
  catalog: Catalog;

  constructor(arg: Stream | ArrayBuffer, callback: ()=>void = null){
    if (isStream(arg)) {
      this._init(arg as Stream);
    }else if (isArrayBuffer(arg)) {
      this._init(new Stream(arg as ArrayBuffer));
    }else {
      error('PDFDocument: Unknown argument type');
    }
  }

  private _init(stream: Stream) {
    assertWellFormed(stream.length > 0, 'stream must have data');
    this.stream = stream;
    this.setup();
    this.acroForm = this.catalog.catDict.get("AcroForm") as Dict;
  }

  private find(stream: Stream, needle: string, limit: number, backwards: boolean = false) {
    let pos:number = stream.pos;
    let end:number = stream.end;
    let str:string = '';
    let vlimit: number = limit
    if (pos + vlimit > end) {
      vlimit = end - pos;
    }
    for (let n = 0; n < vlimit; ++n) {
      str += stream.getChar() ?? '';
    }
    stream.pos = pos;
    let index:number = backwards ? str.lastIndexOf(needle) : str.indexOf(needle);
    if (index == -1) {
      return false;
    }
    stream.pos += index;
    return true;
  }

  get linearization() {
    let length:number = this.stream.length;
    let linearization: Linearization | null;
    if (length > 0) {
      linearization = new Linearization(this.stream);
      if (linearization.length !== length) {
        linearization = null;
      }
    }
    return linearization;
  }

  get startXRef(): number {
    let stream:Stream = this.stream;
    let startXRef:number = 0;
    let linearization:Linearization = this.linearization as Linearization;
    if (linearization !== undefined && linearization !== null) {
      stream.reset();
      if (this.find(stream, 'endobj', Number_1024)) {
        startXRef = stream.pos + Number_6;
      }
    } else {
      let step:number = Number_1024;
      let found:boolean = false;
      let pos:number = stream.end;
      while (!found && pos > 0) {
        pos -= step - 'startxref'.length;
        if (pos < 0) {
          pos = 0;
        }
        stream.pos = pos;
        found = this.find(stream, 'startxref', step, true);
      }
      if (found) {
        stream.skip(Number_9);
        let ch: string;
        do {
          ch = stream.getChar();
        } while (Lexer.isSpace(ch));
        let str:string = '';
        while (ch >= '0' && ch <= '9') {
          str += ch;
          ch = stream.getChar();
        }
        startXRef = Number.parseInt(str);
        if (!isNum(startXRef)) {
          startXRef = 0;
        }
      }
    }
    return startXRef;
  }

  get mainXRefEntriesOffset(): number {
    let mainXRefEntriesOffset:number = 0;
    let linearization: Linearization | null = this.linearization;
    if (linearization !== null) {
      mainXRefEntriesOffset = linearization.mainXRefEntriesOffset;
    }
    return mainXRefEntriesOffset;
  }

  private checkHeader():void {
    let stream:Stream = this.stream;
    stream.reset();
    if (this.find(stream, '%PDF-', Number_1024,false)) {
      stream.moveStart();
      return;
    }
  }

  private setup(ownerPassword: string = null, userPassword: string = null):void {
    this.checkHeader();
    let xref:XRef = new XRef(this.stream, this.startXRef, this.mainXRefEntriesOffset);
    this.xref = xref;
    this.catalog = new Catalog(xref);
  }

  get numPages(): number {
    let linearization: Linearization = this.linearization;
    let num: number = linearization ? linearization.numPages : this.catalog.numPages;
    return num;
  }

  getDocumentInfo():Map<string, string> | undefined {
    let info: Map<string, string> | undefined;
    if (this.xref.trailer.has('Info')) {
      const infoDict:Dict = this.xref.trailer.get('Info') as Dict;
      info = new Map();
      infoDict.forEach((key: string, value: string) => {
        info?.set(key, typeof value !== 'string' ? value : stringToPDFString(value));
      });
    }
    return info;
  }

  getFingerprint():string {
    let xref:XRef = this.xref!;
    let fileID: string = "";
    if (xref.trailer.has('ID')) {
      fileID = '';
      let id: string = xref.trailer.get('ID')[0];
      id.split('').forEach((el: string) => {
        fileID += Number(el.charCodeAt(0)).toString(Number_16);
      });
    } else {
      let data:Uint8Array = this.stream.bytes.subarray(0, Number_100);
      let hash:Uint8Array = calculateMD5(data, 0, data.length);
      fileID = "";
      for (let i = 0; i < hash.length; i++) {
        fileID += Number(hash[i]).toString(Number_16);
      }
    }
    return fileID;
  }

  getPage(n: number):Page {
    return this.catalog.getPage(n) as Page;
  }

}

function log(msg: string): void {
  //PdfJS_window.console.log(String(msg));
}

function warn(msg: string): void {
  if (verbosity >= WARNINGS) {
    log('Warning: ' + msg);
  }
}

function backtrace(): string {
  return "";
}

function error(msg: string): void {
  log('Error: ' + msg);
  log(backtrace());
  throw new Error(msg);
}

function TODO(what: string): void {
  if (verbosity >= TODOS){
    log('TODO: ' + what);
  }
}

function malformed(msg: string): void {
  error(`Malformed PDF: ${msg}`);
}

function assert(cond: boolean, msg: string): void {
  if (!cond){
    error(msg);
  }
}

function assertWellFormed(cond?: boolean, msg?: string): void {
  if (!cond){
    malformed(msg);
  }
}

function bytesToString(bytes: Uint8Array): string {
  let str:string = '';
  for (let n = 0; n < bytes.length; ++n) {
    str += String.fromCharCode(bytes[n]);
  }
  return str;
}

function stringToBytes(str: string): Uint8Array {
  const length = str.length;
  const bytes = new Uint8Array(length).fill(0);
  for (let n = 0; n < length; ++n) {
    bytes[n] = str.charCodeAt(n) & Number_0xFF;
  }
  return bytes;
}

let IDENTITY_MATRIX: number[] = [1, 0, 0, 1, 0, 0];

class Util {
  static makeCssRgb(r: number, g: number, b: number): string {
    const ri:number = (Number_255 * r) | 0;
    const gi:number = (Number_255 * g) | 0;
    const bi:number = (Number_255 * b) | 0;
    return `rgb(${ri},${gi},${bi})`;
  }

  static makeCssCmyk(c: number, m: number, y: number, k: number): string {
    const rgb:number[] = (new DeviceCmykCS()).getRgb([c, m, y, k]);
    const ri:number = (Number_255 * rgb[0]) | 0;
    const gi:number = (Number_255 * rgb[1]) | 0;
    const bi:number = (Number_255 * rgb[Number_2]) | 0;
    return `rgb(${ri},${gi},${bi})`;
  }

  static applyTransform(p: number[], m: number[]): number[] {
    const xt:number = p[0] * m[0] + p[1] * m[Number_2] + m[Number_4];
    const yt:number = p[0] * m[1] + p[1] * m[Number_3] + m[Number_5];
    return [xt, yt];
  }

  static applyInverseTransform(p: number[], m: number[]): number[] {
    const d:number = m[0] * m[Number_3] - m[1] * m[Number_2];
    const xt:number = (p[0] * m[Number_3] - p[1] * m[Number_2] + m[Number_2] * m[Number_5] - m[Number_4] * m[Number_3]) / d;
    const yt:number = (-p[0] * m[1] + p[1] * m[0] + m[Number_4] * m[1] - m[Number_5] * m[0]) / d;
    return [xt, yt];
  }

  static inverseTransform(m: number[]): number[] {
    const d:number = m[0] * m[Number_3] - m[1] * m[Number_2];
    return [m[Number_3] / d, -m[1] / d, -m[Number_2] / d, m[0] / d, (m[Number_2] * m[Number_5] - m[Number_4] * m[Number_3]) / d, (m[Number_4] * m[1] - m[Number_5] * m[0]) / d];
  }

  static apply3dTransform(m: number[], v: number[]): number[] {
    return [m[0] * v[0] + m[1] * v[1] + m[Number_2] * v[Number_2], m[Number_3] * v[0] + m[Number_4] * v[1] + m[Number_5] * v[Number_2], m[Number_6] * v[0] + m[Number_7] * v[1] + m[Number_8] * v[Number_2]
    ];
  }

  static normalizeRect(rect: number[]): number[] {
    const r:number[] = rect.slice(0);
    if (rect[0] > rect[Number_2]) {
      r[0] = rect[Number_2];
      r[Number_2] = rect[0];
    }
    if (rect[1] > rect[Number_3]) {
      r[1] = rect[Number_3];
      r[Number_3] = rect[1];
    }
    return r;
  }

  static intersect(rect1: number[], rect2: number[]): number[] | boolean {
    let compare = (a: number, b: number) => {
      return a - b;
    }

    const orderedX:number[] = [rect1[0], rect1[Number_2], rect2[0], rect2[Number_2]].sort(compare);
    const orderedY:number[] = [rect1[1], rect1[Number_3], rect2[1], rect2[Number_3]].sort(compare);
    const result: number[] = new Array<number>();

    rect1 = Util.normalizeRect(rect1);
    rect2 = Util.normalizeRect(rect2);

    if ((orderedX[0] === rect1[0] && orderedX[1] === rect2[0]) ||
      (orderedX[0] === rect2[0] && orderedX[1] === rect1[0])) {
      result[0] = orderedX[1];
      result[Number_2] = orderedX[Number_2];
    } else {
      return false;
    }

    if ((orderedY[0] === rect1[1] && orderedY[1] === rect2[1]) ||
      (orderedY[0] === rect2[1] && orderedY[1] === rect1[1])) {
      result[1] = orderedY[1];
      result[Number_3] = orderedY[Number_2];
    } else {
      return false;
    }

    return result;
  }

  static sign(num: number): number {
    return num < 0 ? -1 : 1;
  }
}

class PageViewport {
  transform: number[];
  offsetX: number;
  offsetY: number;
  width: number;
  height: number;
  fontScale: number;

  constructor(viewBox: number[], scale: number, rotate: number, offsetX: number, offsetY: number) {
    const centerX:number = (viewBox[Number_2] + viewBox[0]) /Number_2;
    const centerY:number = (viewBox[Number_3] + viewBox[1]) /Number_2;
    let rotateA: number;
    let rotateB: number;
    let rotateC: number;
    let rotateD: number;
    switch (rotate) {
      case -Number_180:
      case Number_180:
        rotateA = -1;
        rotateB = 0;
        rotateC = 0;
        rotateD = 1;
        break;
      case -Number_270:
      case Number_90:
        rotateA = 0;
        rotateB = 1;
        rotateC = 1;
        rotateD = 0;
        break;
      case -Number_90:
      case Number_270:
        rotateA = 0;
        rotateB = -1;
        rotateC = -1;
        rotateD = 0;
        break;
      case Number_360:
      case 0:
      default:
        rotateA = 1;
        rotateB = 0;
        rotateC = 0;
        rotateD = -1;
        break;
    }

    let offsetCanvasX: number;
    let offsetCanvasY: number;
    let width: number;
    let height: number;
    if (rotateA == 0) {
      offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
      offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
      width = Math.abs(viewBox[Number_3] - viewBox[1]) * scale;
      height = Math.abs(viewBox[Number_2] - viewBox[0]) * scale;
    } else {
      offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
      offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
      width = Math.abs(viewBox[Number_2] - viewBox[0]) * scale;
      height = Math.abs(viewBox[Number_3] - viewBox[1]) * scale;
    }

    this.transform = [
      rotateA * scale, rotateB * scale, rotateC * scale, rotateD * scale, offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY, offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY
    ];

    this.offsetX = offsetX;
    this.offsetY = offsetY;
    this.fontScale = scale;
    this.width = width;
    this.height = height;
  }

  convertToViewportPoint(x: number, y: number): number[] {
    return Util.applyTransform([x, y], this.transform);
  }

  convertToViewportRectangle(rect: number[]): number[] {
    const tl:number[] = Util.applyTransform([rect[0], rect[1]], this.transform);
    const br:number[] = Util.applyTransform([rect[Number_2], rect[Number_3]], this.transform);
    return [tl[0], tl[1], br[0], br[1]];
  }

  convertToPdfPoint(x: number, y: number): number[] {
    return Util.applyInverseTransform([x, y], this.transform);
  }
}

let PDFStringTranslateTable: number[] = [
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x192, 0x2044, 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C, 0x201D, 0x2018, 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x141, 0x152, 0x160, 0x178, 0x17D, 0x131, 0x142, 0x153, 0x161, 0x17E, 0, 0x20AC
];

function stringToPDFString(str: string): string {
  let n:number = str.length;
  let str2:string = '';

  if (str[0] === '\xFE' && str[1] === '\xFF') {
    for (let i: number =Number_2; i < n; i +=Number_2) {
      str2 += String.fromCharCode(
        (str.charCodeAt(i) << Number_8) | str.charCodeAt(i + 1))
    }
  } else {
    for (let i: number = 0; i < n; ++i) {
      if (PDFStringTranslateTable.length > i) {
        let code = PDFStringTranslateTable[str.charCodeAt(i)];
        str2 += code ? String.fromCharCode(code) : str.charAt(i);
      }
      else {
        str2 += str.charAt(i);
      }
    }
  }
  return str2;
}

function isBool(v: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): boolean {
  return typeof v === 'boolean';
}

function isInt(v: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): boolean {
  return typeof v === 'number' && ((v | 0) == v);
}

function isNum(v: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): boolean {
  return typeof v == 'number';
}

function isString(v: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): boolean {
  return typeof v === 'string';
}

function isNull(v: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): boolean {
  return v === null;
}

function isName(v: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): boolean {
  return v instanceof Name;
}

function isCmd(v: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name, cmd?: string): boolean {
  return v instanceof Cmd && (!cmd || v.cmd === cmd);
}

function isDict(v: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name, type?: string): boolean {
  return v instanceof Dict && (!type || ((v as Dict).get('Type') as Name).name === type);
}

function isArray(v: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): boolean {
  return v instanceof Array;
}

function isStream(v: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): boolean {
  return typeof v === 'object' && v !== null && ('getChar' in (v as Stream));
}

function isArrayBuffer(v: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): boolean {
  return typeof v === 'object' && v !== null && ('byteLength' in v);
}

function isRef(v: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): boolean {
  return v instanceof Ref;
}

function isPDFFunction(v: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): boolean {
  let fnDict: Dict;
  if (isDict(v)) {
    fnDict = v as Dict;
  } else if (isStream(v)) {
    fnDict = (v as Stream).dict;
  } else {
    return false;
  }
  return fnDict.has('FunctionType');
}

class StatTimer {
  private started: Map<string, number> = new Map<string, number>();
  private times: TimeSpan[] = new Array<TimeSpan>();
  enabled: boolean = true;

  private rpad(str: string, pad: string, length: number): string {
    let paddedStr: string = str;
    while (paddedStr.length < length) {
      paddedStr += pad;
    }
    return paddedStr;
  }

  public time(name: string): void {
    if (!this.enabled) {
      return;
    }
    if (this.started.has(name)) {
      error(`Timer is already running for ${name}`);
    }
    this.started.set(name, Date.now());
  }

  public timeEnd(name: string): void {
    if (!this.enabled) {
      return;
    }
    if (!this.started.has(name)) {
      error(`Timer has not been started for ${name}`);
    }
    let span:TimeSpan = new TimeSpan(name, this.started.get(name), Date.now())
    this.times.push(span);
    this.started.set(name,null);
  }

  public toString(): string {
    const times:TimeSpan[] = this.times;
    let out:string = '';
    let longest:number = 0;
    for (let i = 0; i < times.length; i++) {
      const name: string = times[i].name;
      if (name.length > longest) {
        longest = name.length;
      }
    }
    for (let i = 0; i < times.length; i++) {
      const span:TimeSpan = times[i];
      const duration:number = span.end - span.start;
      out += `${this.rpad(span.name, ' ', longest)} ${duration}ms\n`;
    }
    return out;
  }
}

class PDFDocumentProxy {
  private pdfInfo: PDFInfo;
  private transport: WorkerTransport;

  constructor(pdfInfo: PDFInfo, transport: WorkerTransport) {
    this.pdfInfo = pdfInfo;
    this.transport = transport;
  }

  get numPages(): number {
    return this.pdfInfo.numPages;
  }

  get fingerprint(): string {
    return this.pdfInfo.fingerprint;
  }

  getPage(number: number): PDFPromise {
    return this.transport.getPage(number);
  }

  public getDestinations(): PDFPromise {
    const promise:PDFPromise = new PDFPromise();
    const destinations:Map<string, string> = this.pdfInfo.destinations;
    promise.resolve(destinations);
    return promise;
  }

  public getOutline(): PDFPromise {
    const promise:PDFPromise = new PDFPromise();
    const outline:[] = [...this.pdfInfo.outline] as [];
    promise.resolve(outline);
    return promise;
  }

  public getMetadata(): PDFPromise {
    const promise:PDFPromise = new PDFPromise();
    const info:Map<string, string> = this.pdfInfo.info;
    const metadata: string = this.pdfInfo.metadata;
    promise.resolve({
      info: info,
      metadata: metadata ? new Metadata(metadata) : null
    });
    return promise;
  }

  public destroy(): void {
    this.transport.destroy();
  }
}

class PDFPageProxy {
  pageInfo: PageInfo;
  transport?: WorkerTransport;
  stats: StatTimer;
  objs: PDFObjects;
  renderInProgress: boolean;
  annotationsPromise?: PDFPromise;
  displayReadyPromise?: PDFPromise;
  destroyed?: boolean = false;
  operatorList?: OperatorList;

  constructor(pageInfo: PageInfo, transport: WorkerTransport) {
    this.pageInfo = pageInfo;
    this.transport = transport;
    this.stats = new StatTimer();
    this.stats.enabled = globalScope.PDFJS.enableStats;
    this.objs = transport.objs;
    this.renderInProgress = false;
    this.annotationsPromise = null;
    this.displayReadyPromise = null;
    this.destroyed = false;
    this.operatorList = null;
  }

  private get pageNumber():number {
    return this.pageInfo.pageIndex + 1;
  }

  get rotate():number {
    return this.pageInfo.rotate;
  }

  get ref():Ref {
    return this.pageInfo.ref;
  }

  get view():number[] {
    return this.pageInfo.view;
  }

  getViewport(scale: number, rotate: number = null): PageViewport {
    let rotateValue:number = rotate ?? this.rotate;
    return new PageViewport(this.view, scale, rotateValue, 0, 0);
  }

  getAnnotations(): PDFPromise {
    if (this.annotationsPromise !== null && this.annotationsPromise !== undefined){
      return this.annotationsPromise;
    }
    const promise:PDFPromise = new PDFPromise();
    this.annotationsPromise = promise;
    this.transport.getAnnotations(this.pageInfo.pageIndex);
    return promise;
  }

  render(params: RenderContext):PDFPromise {
    this.renderInProgress = true;

    const promise:PDFPromise = new PDFPromise();
    const stats:StatTimer = this.stats;
    stats.time('Overall');
    if (this.displayReadyPromise === null || this.displayReadyPromise === undefined){
      this.displayReadyPromise = new PDFPromise();
      this.destroyed = false;

      this.stats.time('Page Request');
      this.transport.messageHandler.send('RenderPageRequest', {
        pageIndex: this.pageNumber - 1
      });
    }

    let complete = (error?: string) => {
      this.renderInProgress = false;

      if (this.destroyed) {
        this.operatorList = null;
        this.displayReadyPromise = null;
      }

      if (error) {
        promise.reject(error);
      } else {
        promise.resolve();
      }
    };

    this.displayReadyPromise.then(
      (): void => {
        if (this.destroyed) {
          complete();
          return;
        }
        const gfx: CanvasGraphics = new CanvasGraphics(params.canvasContext, this.objs, params.textLayer);
        try {
          this.display(gfx, params.viewport, complete);
        } catch (e) {
          complete(e);
        }
      },
      (reason: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): void => {
        complete(reason as string);
      }
    );
    return promise;
  }

  startRenderingFromOperatorList(operatorList: OperatorList, fonts: string[]) {
    this.operatorList = operatorList;

    const displayContinuation = (): void => {

      PdfJS_window.setTimeout(() => {
        this.displayReadyPromise.resolve();
      });
    };

    this.ensureFonts(fonts,
      (): void => {
        displayContinuation();
      }
    );
  }

  ensureFonts(fonts: Font[] | string[] | Record<string, string>[], callback: () => void) {
    this.stats.time('Font Loading');

    for (let i: number = 0; i < (fonts as []).length; i++) {
      fonts[i] = this.objs.objs.get(fonts[i] as string).data as Record<string, string>;
    }
    FontLoader.loaderBind(
      fonts as Font[], () => {
        this.stats.timeEnd('Font Loading');
        callback.call(this);
      }
    );
  }

  display(gfx: CanvasGraphics, viewport: PageViewport, callback: () => void) {
    const stats:StatTimer = this.stats;
    stats.time('Rendering');

    gfx.beginDrawing(viewport);

    let startIdx:number = 0;
    const length: number = this.operatorList.fnArray.length;
    const operatorList:OperatorList = this.operatorList;
    let stepper: Stepper;
    if (PDFJS.pdfBug && StepperManager.enabled) {
      stepper = StepperManager.create(this.pageNumber - 1);
      stepper._init(operatorList);
      stepper.nextBreakPoint = stepper.getNextBreakPoint();
    }
    let next = () => {
      if (startIdx === 0) {
      }
      startIdx = gfx.executeOperatorList(operatorList, startIdx, next, stepper);
      if (startIdx == length) {
        gfx.endDrawing();
        stats.timeEnd('Rendering');
        stats.timeEnd('Overall');
        if (callback !== null && callback !== undefined) {
          callback();
        }
      }
    }
    next();
  }

  getTextContent(): PDFPromise {
    const promise:PDFPromise = new PDFPromise();
    const textContent:string = 'page text';
    promise.resolve(textContent);
    return promise;
  }

  getOperationList(): PDFPromise {
    const promise:PDFPromise = new PDFPromise();
    const operationList = {
      dependencyFontsID: null,
      operatorList: null
    };
    promise.resolve(operationList);
    return promise;
  }

  destroy(): void {
    this.destroyed = true;

    if (!this.renderInProgress) {
      this.operatorList = null;
      this.displayReadyPromise = null;
    }
  }
}

class WorkerTransport {
  private workerReadyPromise: PDFPromise;
  objs: PDFObjects;
  private pageCache: number[] | PageInfo[] | PDFPageProxy[] | Page[];
  private pagePromises: PDFPromise[];
  fontsLoading: Map<string , string | number>;
  pdfDocument: PDFDocumentProxy
  private worker?: Worker;
  messageHandler: MessageHandler;
  constructor(promise: PDFPromise) {
    this.workerReadyPromise = promise;
    this.objs = new PDFObjects();
    this.pageCache = new Array();
    this.pagePromises = new Array< PDFPromise>();
    this.fontsLoading = new Map();

    if (!globalScope.PDFJS.disableWorker && PdfJS_window.Worker !== undefined) {
      const workerSrc:string = PDFJS.workerSrc;
      if (workerSrc === undefined) {
        error('No PDFJS.workerSrc specified');
      }

      try {
        let worker: Worker = new Worker('');
        if (PDFJS.isFirefoxExtension) {
        } else {
          worker = new Worker(workerSrc);
        }

        const messageHandler: MessageHandler = new MessageHandler('main', worker);
        this.messageHandler = messageHandler;

        messageHandler.on('test', (supportTypedArray: boolean) => {
          if (supportTypedArray) {
            this.worker = worker;
            this.setupMessageHandler(messageHandler);
          } else {
            globalScope.PDFJS.disableWorker = true;
            this.setupFakeWorker();
          }
        });

        const testObj:Uint8Array = new Uint8Array(1);
        messageHandler.send('test', testObj);
        return;
      } catch (e) {
        warn('The worker has been disabled.');
      }
    }
    globalScope.PDFJS.disableWorker = true;
    this.setupFakeWorker();
  }

  destroy() {
    if (this.worker !== null && this.worker !== undefined){
      this.worker.terminate();
    }

    this.pageCache = new Array();
    this.pagePromises = new Array<PDFPromise>();
  }

  private setupFakeWorker() {
    const fakeWorker: FakeWorker = new FakeWorker();
    const messageHandler:MessageHandler = new MessageHandler('main', fakeWorker);
    this.setupMessageHandler(messageHandler);
    WorkerMessageHandler.setup(messageHandler);
  }

  private setupMessageHandler(messageHandler: MessageHandler) {
    this.messageHandler = messageHandler;

    messageHandler.on("GetDoc", (data: Record<string,PDFInfo>) => {
      const pdfInfo: PDFInfo = data["pdfInfo"];
      let pdfDocument:PDFDocumentProxy = new PDFDocumentProxy(pdfInfo, this);
      this.pdfDocument = pdfDocument;
      this.workerReadyPromise.resolve(pdfDocument);
    }, this);

    messageHandler.on("GetPage", (data: Record<string,PageInfo>) => {
      const pageInfo: PageInfo = data["pageInfo"];
      const page:PDFPageProxy = new PDFPageProxy(pageInfo, this);
      this.pageCache[pageInfo.pageIndex] = page;
      const promise: PDFPromise = this.pagePromises[pageInfo.pageIndex];
      promise.resolve(page);
    }, this);

    messageHandler.on("GetAnnotations", (data: Record<string,object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>) => {
      const annotations: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = data["annotations"];
      const promise:PDFPromise = (this.pageCache[data["pageIndex"] as number] as PDFPageProxy).annotationsPromise;
      promise.resolve(annotations);
    }, this);

    messageHandler.on("RenderPage", (data: Record<string,object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>) => {
      const page: PDFPageProxy = this.pageCache[data["pageIndex"] as number] as PDFPageProxy;
      const depFonts: [] = data["depFonts"] as [];
      page.stats.timeEnd('Page Request');
      page.startRenderingFromOperatorList(data["operatorList"] as OperatorList, depFonts);
    }, this);

    messageHandler.on("obj", (data: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[]) => {
      const id:string = data[0] as string;
      const type:string = data[1] as string;
      if (this.objs.hasData(id)) {
        return;
      }

      switch (type) {
        case 'JpegStream':
          let imageDatas:string = data[Number_2] as string;
          loadJpegStream(id, imageDatas, this.objs);
          break;
        case 'Image':
          let imageData:string = data[Number_2] as string;
          this.objs.resolve(id, imageData);
          break;
        case 'Font':
          const name:string = data[Number_2] as string;
          let file: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = data[Number_3];
          const properties:FontProperties= data[Number_4] as FontProperties;
          if (file !== null && file !== undefined) {
            const fontFileDict:Dict = new Dict();
            file = new Stream(file as Uint8Array, 0, (file as Uint8Array).length, fontFileDict);
          }
          const font:Font = new Font(name, file as Stream, properties);
          this.objs.resolve(id, font);
          break;
        default:
          error(`Got unknown object type ${type}`);
      }
    }, this);

    messageHandler.on("PageError", (data: Map<string,PDFInfo>) => {
      const page: Page = this.pageCache[data["pageNum"] - 1] as Page;
      if (page.displayReadyPromise !== null && page.displayReadyPromise !== undefined) {
        page.displayReadyPromise.reject(data["error"]);
      } else {
        error(data["error"]);
      }
    }, this);


    messageHandler.on("JpegDecode", (data: [string , number], promise: PDFPromise) => {
      const imageData:string = data[0];
      const components:number = data[1];
      if (components !== Number_3 && components !== 1) {
        error('Only 3 component or 1 component can be returned');
      }
      const img:Image = new Image();
      img.onload = () => {
        const width: number = img.width;
        const height: number = img.height;
        const size:number = width * height;
        const rgbaLength:number = size * Number_4;
        const buf:Uint8Array = new Uint8Array(size * components).fill(0);
        const tmpCanvas:Canvas = createScratchCanvas(width, height);
        const tmpCtx:PDFContext = tmpCanvas.getContext('2d');
        tmpCtx.drawImage(img, 0, 0);
        const data:Uint8Array = tmpCtx.getImageData(0, 0, width, height).data;

        if (components == Number_3) {
          var j = 0;
          for (var i = 0; i < rgbaLength; i += Number_4) {
            buf[j] = data[i];
            buf[j + 1] = data[i + 1];
            buf[j +Number_2] = data[i +Number_2];
            j += Number_3;
          }
        } else if (components == 1) {
          var j = 0;
          for (var i = 0; i < rgbaLength; i += Number_4) {
            buf[j] = data[i];
            j += 1;
          }
        }
        promise.resolve({ data: buf, width: width, height: height });
      }
      const src:string = 'data:image/jpeg;base64,' + PdfJS_window.btoa(imageData);
      img.src = src;
    }, this);
  }

  sendData(data: Uint8Array) {
    this.messageHandler.send('GetDocRequest', data);

  }

  getPage(pageNumber: number, promise?: PDFPromise): PDFPromise {
    const pageIndex:number = pageNumber - 1;
    if (pageIndex < this.pagePromises.length) {
      return this.pagePromises[pageIndex];
    }
    const newPromise: PDFPromise = new PDFPromise('Page ' + pageNumber);
    this.pagePromises[pageIndex] = newPromise;
    this.messageHandler.send("GetPageRequest", {pageIndex: pageIndex});
    return newPromise;
  }

  getAnnotations(pageIndex: number) {
    this.messageHandler.send('GetAnnotationsRequest', {pageIndex: pageIndex});
  }
}

enum TextRenderingMode {
  FILL = 0,
  STROKE = 1,
  FILL_STROKE,
  INVISIBLE,
  FILL_ADD_TO_PATH,
  STROKE_ADD_TO_PATH,
  FILL_STROKE_ADD_TO_PATH,
  ADD_TO_PATH
}

let MIN_FONT_SIZE: number = 1.0;

function createScratchCanvas(width: number, height: number):Canvas {
  let canvas: Canvas = PdfJS_window.document.createElement('canvas') as Canvas;
  canvas.width = width;
  canvas.height = height;
  return canvas;
}

class PdfJSContextExt extends PDFContext {
  save(): void {
    const old: number[] = this._transformMatrix;
    this._transformStack.push(old);
    this._transformMatrix = old.slice(0, Number_6);
    super.save();
  }

  restore(): void {
    const prev: number[] | undefined = this._transformStack.pop();
    if (prev !== undefined) {
      this._transformMatrix = prev;
    }
    super.restore();
  }

  translate(x: number, y: number): void {
    const m: number[] = this._transformMatrix;
    m[Number_4] = m[0] * x + m[Number_2] * y + m[Number_4];
    m[Number_5] = m[1] * x + m[Number_3] * y + m[Number_5];
    super.translate(x, y);
  }

  scale(x: number, y: number): void {
    const m: number[] = this._transformMatrix;
    m[0] *= x;
    m[1] *= x;
    m[Number_2] *= y;
    m[Number_3] *= y;
    super.scale(x, y);
  }

  transform(a: number, b: number, c: number, d: number, e: number, f: number): void {
    const m: number[] = this._transformMatrix;
    this._transformMatrix = [
      m[0] * a + m[Number_2] * b, m[1] * a + m[Number_3] * b, m[0] * c + m[Number_2] * d, m[1] * c + m[Number_3] * d, m[0] * e + m[Number_2] * f + m[Number_4], m[1] * e + m[Number_3] * f + m[Number_5],
    ];
    super.transform(a, b, c, d, e, f);
  }

  rotate(angle: number): void {
    const cosValue: number = Math.cos(angle);
    const sinValue: number = Math.sin(angle);

    const m: number[] = this._transformMatrix;
    this._transformMatrix = [
      m[0] * cosValue + m[Number_2] * sinValue, m[1] * cosValue + m[Number_3] * sinValue, m[0] * (-sinValue) + m[Number_2] * cosValue, m[1] * (-sinValue) + m[Number_3] * cosValue, m[Number_4], m[Number_5],
    ];
    super.rotate(angle);
  }

  mozCurrentTransformInverseFunc(): number[] {
    const m: number[] = this._transformMatrix;
    const a: number = m[0];
    const b: number = m[1];
    const c: number = m[Number_2];
    const d: number = m[Number_3];
    const e: number = m[Number_4];
    const f: number = m[Number_5];

    const ad_bc: number = a * d - b * c;
    const bc_ad: number = b * c - a * d;

    return [
      d / ad_bc, b / bc_ad, c / bc_ad, a / ad_bc, (d * e - c * f) / bc_ad, (b * e - a * f) / ad_bc,
    ];
  }

  static addContextCurrentTransform(ctx: PDFContext): void {
    if (ctx.mozCurrentTransform !== undefined) {
      ctx._originalSave = ctx.save;
      ctx._originalRestore = ctx.restore;
      ctx._originalRotate = ctx.rotate;
      ctx._originalScale = ctx.scale;
      ctx._originalTranslate = ctx.translate;
      ctx._originalTransform = ctx.transform;

      ctx._transformMatrix = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0];
      ctx._transformStack = new Array<number[]>();

      ctx.mozCurrentTransform = ctx._transformMatrix;
    }
  }
}

class CanvasExtraState {
  alphaIsShape: boolean;
  fontSize: number;
  fontSizeScale: number;
  textMatrix: number[];
  fontMatrix: number[];
  leading: number;
  x: number;
  y: number;
  lineX: number;
  lineY: number;
  charSpacing: number;
  wordSpacing: number;
  textHScale: number;
  textRenderingMode: TextRenderingMode;
  fillColorSpace: DeviceGrayCS | PatternCS;
  fillColorSpaceObj: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
  strokeColorSpace: DeviceGrayCS | PatternCS;
  strokeColorSpaceObj: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
  fillColorObj: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
  strokeColorObj: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
  fillColor: Pattern | string;
  strokeColor: Pattern | string;
  fillAlpha: number;
  strokeAlpha: number;
  lineWidth: number;
  old: CanvasExtraState;
  font: Font;
  constructor(old: CanvasExtraState = null) {
    this.alphaIsShape = false;
    this.fontSize = 0.0;
    this.fontSizeScale = 1.0;
    this.textMatrix = IDENTITY_MATRIX;
    this.fontMatrix = IDENTITY_MATRIX;
    this.leading = 0.0;
    this.x = 0.0;
    this.y = 0.0;
    this.lineX = 0.0;
    this.lineY = 0.0;
    this.charSpacing = 0.0;
    this.wordSpacing = 0.0;
    this.textHScale = 1.0;
    this.textRenderingMode = TextRenderingMode.FILL;
    this.fillColorSpace = new DeviceGrayCS();
    this.fillColorSpaceObj = null;
    this.strokeColorSpace = new DeviceGrayCS();
    this.strokeColorSpaceObj = null;
    this.fillColorObj = null;
    this.strokeColorObj = null;
    this.fillColor = '#000000';
    this.strokeColor = '#000000';
    this.fillAlpha = 1.0;
    this.strokeAlpha = 1.0;
    this.lineWidth = 1.0;
    this.old = old;
  }

  clone(): CanvasExtraState {
    return this;
  }

  setCurrentPoint(x: number, y: number): void {
    this.x = x;
    this.y = y;
  }
}

class CanvasGraphics {
  kExecutionTime = Number_15;
  LINE_CAP_STYLES: string[] = ['butt', 'round', 'square'];
  LINE_JOIN_STYLES: string[] = ['miter', 'round', 'bevel'];
  NORMAL_CLIP: string = "NORMAL_CLIP";
  EO_CLIP: string = "EO_CLIP";
  slowCommands: Record<string, boolean> = {
    stroke: true,
    closeStroke: true,
    fill: true,
    eoFill: true,
    fillStroke: true,
    eoFillStroke: true,
    closeFillStroke: true,
    closeEOFillStroke: true,
    showText: true,
    showSpacedText: true,
    setStrokeColorSpace: true,
    setFillColorSpace: true,
    setStrokeColor: true,
    setStrokeColorN: true,
    setFillColor: true,
    setFillColorN: true,
    setStrokeGray: true,
    setFillGray: true,
    setStrokeRGBColor: true,
    setFillRGBColor: true,
    setStrokeCMYKColor: true,
    setFillCMYKColor: true,
    paintJpegXObject: true,
    paintImageXObject: true,
    paintImageMaskXObject: true,
    shadingFill: true,
  };
  ctx: PDFContext;
  current: CanvasExtraState;
  stateStack: CanvasExtraState[];
  pendingClip: string | null;
  res: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
  xobjs: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
  objs: PDFObjects;
  textLayer: TextLayer;

  constructor(canvasCtx?: PDFContext, objs?: PDFObjects, textLayer?: TextLayer) {
    this.ctx = canvasCtx;
    this.current = new CanvasExtraState();
    this.stateStack = new Array<CanvasExtraState>();
    this.pendingClip = null;
    this.res = null;
    this.xobjs = null;
    this.objs = objs;
    this.textLayer = textLayer;
    if (canvasCtx !== undefined && canvasCtx !== null) {
      PdfJSContextExt.addContextCurrentTransform(canvasCtx);
    }
  }

  _customApply(name: string, args: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[]): void {
    switch (name) {
      case "beginText":
        this.beginText();
        break;
      case "setFont":
        this.setFont(args[0] as string, args[1] as number);
        break;
      case "moveText":
        this.moveText((args as number[])[0], (args as number[])[1]);
        break;
      case "showSpacedText":
        this.showSpacedText(args[0] as (string | number)[]);
        break;
      case "moveTo":
        this.moveTo((args as number[])[0], (args as number[])[1]);
        break;
      case "lineTo":
        this.lineTo((args as number[])[0], (args as number[])[1]);
        break;
      case "stroke":
        this.stroke();
        break;
      case "save":
        this.save();
        break;
      case "transform":
        this.transform((args as number[])[0], (args as number[])[1], (args as number[])[Number_2], (args as number[])[Number_3], (args as number[])[Number_4], (args as number[])[Number_5]);
        break;
      case "setDash":
        this.setDash(args[0] as number[], (args as number[])[1]);
        break;
      case "setLineCap":
        this.setLineCap(args[0] as number);
        break;
      case "setLineWidth":
        this.setLineWidth(args[0] as number);
        break;
      case "restore":
        this.restore();
        break;
      default:
        return;
    }
  }

  public beginDrawing(viewport: PageViewport): void {
    const transform: number[] = viewport.transform;
    this.ctx.save();
    this.ctx.transform(transform[0], transform[1], transform[Number_2], transform[Number_3], transform[Number_4], transform[Number_5]);
    if (this.textLayer !== null && this.textLayer !== undefined) {
      this.textLayer.beginLayout();
    }
  }

  executeOperatorList(
    operatorList: OperatorList,
    executionStartIdx = 0,
    continueCallback?: () => void,
    stepper?: Stepper
  ): number {
    let argsArray:string[][] = operatorList.argsArray as string[][];
    let fnArray:string[] = operatorList.fnArray as string[];
    let i:number = executionStartIdx;
    const argsArrayLen:number = argsArray.length;

    if (argsArrayLen === i) {
      return i;
    }

    let executionEndIdx: number;
    let endTime:number = Date.now() + this.kExecutionTime;
    const objs:PDFObjects = this.objs;
    let fnName: string;
    const slowCommands:Record<string, boolean> = this.slowCommands;

    while (true) {
      if (stepper && i === stepper.nextBreakPoint) {
        stepper.breakIt(i, continueCallback);
        return i;
      }

      fnName = fnArray[i];
      if (fnName !== 'dependency') {
        this._customApply(fnName, argsArray[i]);
      } else {
        const deps:string[] = argsArray[i];
        for (let n = 0; n < deps.length; n++) {
          const depObjId:string = deps[n];
          if (!objs.isResolved(depObjId)) {
            objs.get(depObjId, continueCallback);
            return i;
          }
        }
      }

      i+=1;

      if (i === argsArrayLen) {
        return i;
      }

      if (continueCallback && slowCommands[fnName] && Date.now() > endTime) {
        PdfJS_window.setTimeout(continueCallback, 0);
        return i;
      }
    }
  }

  endDrawing(): void {
    this.ctx.restore();
    if (this.textLayer !== null && this.textLayer !== undefined) {
      this.textLayer.endLayout();
    }
  }

  setLineWidth(width: number): void {
    this.current.lineWidth = width;
    this.ctx.lineWidth = width;
  }

  setLineCap(style: number): void {
    this.ctx.lineCap = this.LINE_CAP_STYLES[style];
  }

  setLineJoin(style: number): void {
    this.ctx.lineJoin = this.LINE_JOIN_STYLES[style];
  }

  setMiterLimit(limit: number): void {
    this.ctx.miterLimit = limit;
  }

  setDash(dashArray: number[], dashPhase: number): void {
    this.ctx.mozDash = dashArray;
    this.ctx.mozDashOffset = dashPhase;
    this.ctx.webkitLineDash = dashArray;
    this.ctx.webkitLineDashOffset = dashPhase;
  }

  setRenderingIntent(intent: string): void {
  }

  setFlatness(flatness: number): void {
  }

  setGState(states: number[]): void {
    for (let i: number = 0; i < states.length; i++) {
      const state: number = states[i];
      const key:string = state[0];
      const value: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = state[1];

      switch (key) {
        case 'LW':
          this.setLineWidth(value as number);
          break;
        case 'LC':
          this.setLineCap(value as number);
          break;
        case 'LJ':
          this.setLineJoin(value as number);
          break;
        case 'ML':
          this.setMiterLimit(value as number);
          break;
        case 'D':
          this.setDash(value[0], value[1]);
          break;
        case 'RI':
          this.setRenderingIntent(value as string);
          break;
        case 'FL':
          this.setFlatness(value as number);
          break;
        case 'Font':
          this.setFont(state[1], state[Number_2]);
          break;
        case 'CA':
          this.current.strokeAlpha = value as number;
          break;
        case 'ca':
          this.current.fillAlpha = value as number;
          this.ctx.globalAlpha = value as number;
          break;
      }
    }
  }

  save(): void {
    this.ctx.save();
    let old:CanvasExtraState = this.current;
    this.stateStack.push(old);
    this.current = old.clone();
  }

  restore(): void {
    const prev:CanvasExtraState = this.stateStack.pop();
    if (prev !== null && prev !== undefined) {
      this.current = prev;
      this.ctx.restore();
    }
  }

  transform(a: number, b: number, c: number, d: number, e: number, f: number): void {
    this.ctx.transform(a, b, c, d, e, f);
  }

  moveTo(x: number, y: number): void {
    this.ctx.moveTo(x, y);
    this.current.setCurrentPoint(x, y);
  }

  lineTo(x: number, y: number): void {
    this.ctx.lineTo(x, y);
    this.current.setCurrentPoint(x, y);
  }

  curveTo(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): void {
    this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
    this.current.setCurrentPoint(x3, y3);
  }

  curveTo2(x2: number, y2: number, x3: number, y3: number): void {
    let current: CanvasExtraState = this.current;
    this.ctx.bezierCurveTo(current.x, current.y, x2, y2, x3, y3);
    current.setCurrentPoint(x3, y3);
  }

  curveTo3(x1: number, y1: number, x3: number, y3: number): void {
    this.curveTo(x1, y1, x3, y3, x3, y3);
    this.current.setCurrentPoint(x3, y3);
  }

  closePath(): void {
    this.ctx.closePath();
  }

  rectangle(x: number, y: number, width: number, height: number): void {
    this.ctx.rect(x, y, width, height);
  }

  stroke(consumePath: boolean = true): void {
    let ctx:PDFContext = this.ctx;
    let strokeColor: Pattern | string = this.current.strokeColor;
    if (this.current.lineWidth === 0){
      ctx.lineWidth = this.getSinglePixelWidth();
    }
    ctx.globalAlpha = this.current.strokeAlpha;
    if (strokeColor instanceof Pattern) {
      ctx.save();
      ctx.strokeStyle = strokeColor!.getPattern(ctx) as string;
      ctx.stroke();
      ctx.restore();
    } else {
      ctx.stroke();
    }
    if (consumePath) {
      this.consumePath();
    }
    ctx.globalAlpha = this.current.fillAlpha;
  }

  closeStroke(): void {
    this.closePath();
    this.stroke();
  }

  fill(consumePath: boolean = true): void {
    let ctx:PDFContext = this.ctx;
    let fillColor: Pattern = this.current.fillColor as Pattern;
    if (fillColor !== null && fillColor !== undefined){
      ctx.save();
      ctx.fillStyle = fillColor.getPattern(ctx);
      ctx.fill();
      ctx.restore();
    } else {
      ctx.fill();
    }
    if (consumePath) {
      this.consumePath();
    }
  }

  eoFill(): void {
    let savedFillRule:string = this.setEOFillRule();
    this.fill();
    this.restoreFillRule(savedFillRule);
  }

  fillStroke(): void {
    this.fill(false);
    this.stroke(false);

    this.consumePath();
  }

  eoFillStroke(): void {
    let savedFillRule:string = this.setEOFillRule();
    this.fillStroke();
    this.restoreFillRule(savedFillRule);
  }

  closeFillStroke(): void {
    this.closePath();
    this.fillStroke();
  }

  closeEOFillStroke(): void {
    let savedFillRule:string = this.setEOFillRule();
    this.closePath();
    this.fillStroke();
    this.restoreFillRule(savedFillRule);
  }

  endPath(): void {
    this.consumePath();
  }

  clip(): void {
    this.pendingClip = this.NORMAL_CLIP;
  }

  eoClip(): void {
    this.pendingClip = this.EO_CLIP;
  }

  beginText(): void {
    this.current.textMatrix = IDENTITY_MATRIX;
    this.current.x = 0.0;
    this.current.lineX = 0.0;
    this.current.y = 0.0;
    this.current.lineY = 0.0;
  }

  endText(): void {
  }

  setCharSpacing(spacing: number): void {
    this.current.charSpacing = spacing;
  }

  setWordSpacing(spacing: number): void {
    this.current.wordSpacing = spacing;
  }

  setHScale(scale: number): void {
    this.current.textHScale = scale / Number_100;
  }

  setLeading(leading: number): void {
    this.current.leading = -leading;
  }

  setFont(fontRefName: string, size: number): void {
    let fontObj: Font = this.objs.get(fontRefName) as Font;
    let current:CanvasExtraState = this.current;

    if (fontObj === null || fontObj === undefined) {
      error(`Can't find font for ${fontRefName}`);
    }
    if (fontObj.fontMatrix !== null && fontObj.fontMatrix !== undefined){
      current.fontMatrix = fontObj.fontMatrix;
    } else {
      current.fontMatrix = IDENTITY_MATRIX;
    }

    if (current.fontMatrix[0] === 0 || current.fontMatrix[Number_3] === 0) {
      warn(`Invalid font matrix for font ${fontRefName}`);
    }
    size = size
    if (size < 0) {
      size = -size;
      current.fontMatrix[0] *= -1;
      current.fontMatrix[Number_3] *= -1;
    }

    this.current.font = fontObj;
    this.current.fontSize = size;

    if (fontObj.coded) {
      return;
    }

    let name:string = fontObj.loadedName || 'sans-serif';
    let bold:string = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') : (fontObj.bold ? 'bold' : 'normal');
    let italic:string = fontObj.italic ? 'italic' : 'normal';
    let serif:string = fontObj.isSerifFont ? 'serif' : 'sans-serif';
    let typeface:string = `"${name}", ${serif}`;

    let browserFontSize: number = size >= MIN_FONT_SIZE ? size : MIN_FONT_SIZE;
    this.current.fontSizeScale = browserFontSize !== MIN_FONT_SIZE ? 1.0 : size / MIN_FONT_SIZE;

    let rule:string = `${italic} ${bold} ${browserFontSize}px ${typeface}`;
    this.ctx.font = rule;
  }

  setTextRenderingMode(mode: number): void {
    if (mode >= TextRenderingMode.FILL_ADD_TO_PATH) {
      TODO(`unsupported text rendering mode: ${mode}`);
    }
    this.current.textRenderingMode = mode;
  }

  setTextRise(rise: number): void {
    TODO(`text rise: ${rise}`);
  }

  moveText(x: number, y: number): void {
    this.current.x += x;
    this.current.lineX += x;
    this.current.y += x;
    this.current.lineY += y;
  }

  setLeadingMoveText(x: number, y: number): void {
    this.setLeading(-y);
    this.moveText(x, y);
  }

  setTextMatrix(a: number, b: number, c: number, d: number, e: number, f: number): void {
    this.current.textMatrix = [a, b, c, d, e, f];

    this.current.x = 0;
    this.current.lineX = 0;
    this.current.y = 0;
    this.current.lineY = 0;
  }

  nextLine(): void {
    this.moveText(0, this.current.leading);
  }

  applyTextTransforms(): void {
    const ctx:PDFContext = this.ctx;
    const current:CanvasExtraState = this.current;
    const textHScale: number = current.textHScale;
    const fontMatrix:number[] = current.fontMatrix || IDENTITY_MATRIX;
    ctx.transform(current.textMatrix[0], current.textMatrix[1], current.textMatrix[Number_2], current.textMatrix[Number_3],
      current.textMatrix[Number_4], current.textMatrix[Number_5])
    ctx.scale(1, -1);
    ctx.translate(current.x, -1 * current.y);
    ctx.transform(fontMatrix[0], fontMatrix[1], fontMatrix[Number_2], fontMatrix[Number_3], fontMatrix[Number_4], fontMatrix[Number_5])
    ctx.scale(textHScale, 1);
  }

  getTextGeometry(): Geometry {
    let geometry: Geometry;
    const ctx:PDFContext = this.ctx;
    const font:Font = this.current.font;
    const ctxMatrix = ctx.mozCurrentTransform;
    if (ctxMatrix !== null && ctxMatrix !== undefined) {
      const bl:number[] = Util.applyTransform([0, 0], ctxMatrix);
      const tr:number[] = Util.applyTransform([1, 1], ctxMatrix);
      geometry.x = bl[0];
      geometry.y = bl[1];
      geometry.hScale = tr[0] - bl[0];
      geometry.vScale = tr[1] - bl[1];
    }
    geometry.spaceWidth = font.spaceWidth;
    return geometry;
  }

  showText(str: string, skipTextSelection: boolean = false): BidiText {
    const ctx:PDFContext = this.ctx;
    const current:CanvasExtraState = this.current;
    const font:Font = current.font;
    const glyphs:GlyphsStruct[] = font.charsToGlyphs(str) as GlyphsStruct[];
    const fontSize:number = current.fontSize;
    const fontSizeScale:number = current.fontSizeScale;
    const charSpacing: number = current.charSpacing;
    const wordSpacing:number = current.wordSpacing;
    const textHScale:number = current.textHScale;
    const fontMatrix:number[] = current.fontMatrix || IDENTITY_MATRIX;
    const textHScale2:number = textHScale * fontMatrix[0];
    const glyphsLength:number = glyphs.length;
    const textLayer:TextLayer = this.textLayer;
    const text:BidiText = new BidiText('', 0, 0, null);
    const textSelection:boolean = textLayer !== null && !skipTextSelection;
    const textRenderingMode: TextRenderingMode = current.textRenderingMode;

    if (font.coded) {
      ctx.save();
      ctx.transform(current.textMatrix[0], current.textMatrix[1], current.textMatrix[Number_2], current.textMatrix[Number_3],
        current.textMatrix[Number_4], current.textMatrix[Number_5])
      ctx.translate(current.x, current.y);

      ctx.scale(textHScale, 1);

      if (textSelection) {
        this.save();
        ctx.scale(1, -1);
        text.geom = this.getTextGeometry();
        this.restore();
      }
      for (let i = 0; i < glyphsLength; ++i) {
        const glyph:GlyphsStruct = glyphs[i];
        if (glyph === null) {
          this.ctx.translate(wordSpacing, 0);
          continue;
        }

        this.save();
        ctx.scale(fontSize, fontSize);
        ctx.transform(fontMatrix[0], fontMatrix[0], fontMatrix[0], fontMatrix[0], fontMatrix[0], fontMatrix[0])
        this.executeOperatorList(glyph.operatorList);
        this.restore();

        const transformed:number[] = Util.applyTransform([glyph.width, 0], fontMatrix);
        const width:number = transformed[0] * fontSize + Util.sign(current.fontMatrix[0]) * charSpacing;

        ctx.translate(width, 0);
        current.x += width * textHScale;

        text.str += String(glyph.unicode);
        text.length += 1;
        text.canvasWidth += width;
      }
      ctx.restore();
    } else {
      ctx.save();
      this.applyTextTransforms();

      let lineWidth: number = current.lineWidth;
      const scale: number = Math.abs(current.textMatrix[0] * fontMatrix[0]);
      if (scale == 0 || lineWidth == 0) {
        lineWidth = this.getSinglePixelWidth();
      } else {
        lineWidth /= scale;
      }

      if (textSelection) {
        text.geom = this.getTextGeometry();
      }

      if (fontSizeScale !== 1.0) {
        ctx.scale(fontSizeScale, fontSizeScale);
        lineWidth /= fontSizeScale;
      }

      ctx.lineWidth = lineWidth;

      let x:number = 0.0;
      for (let i = 0; i < glyphsLength; ++i) {
        const glyph:GlyphsStruct = glyphs[i];
        if (glyph === null) {
          x += Util.sign(current.fontMatrix[0]) * wordSpacing;
          continue;
        }

        const character:string = glyph.fontChar;
        const charWidth:number = glyph.width * fontSize * Number_0001 + Util.sign(current.fontMatrix[0]) * charSpacing;

        if (!glyph.disabled) {
          const scaledX: number = x / fontSizeScale;
          switch (textRenderingMode) {
            case TextRenderingMode.FILL:
            case TextRenderingMode.FILL_ADD_TO_PATH:
              ctx.fillText(character!, scaledX, 0.0);
              break;
            case TextRenderingMode.STROKE:
            case TextRenderingMode.STROKE_ADD_TO_PATH:
              ctx.strokeText(character, scaledX, 0.0);
              break;
            case TextRenderingMode.FILL_STROKE:
            case TextRenderingMode.FILL_STROKE_ADD_TO_PATH:
              ctx.fillText(character, scaledX, 0.0);
              ctx.strokeText(character, scaledX, 0.0);
              break;
            case TextRenderingMode.INVISIBLE:
              break;
            default:
              break;
          }
        }
        x += charWidth;

        let glyphUnicode: string;
        if(String(glyph.unicode) === ' '){
          glyphUnicode = '\u00A0';
        } else {
          glyphUnicode = String(glyph.unicode);
        }

        const glyphUnicodeLength:number = glyphUnicode.length;
        if (glyphUnicodeLength > 1 && isRTLRangeFor(glyphUnicode.charCodeAt(0))) {
          for (let ii = glyphUnicodeLength - 1; ii >= 0; ii--) {
            text.str += glyphUnicode[ii];
          }
        } else {
          text.str += glyphUnicode;
        }
        text.length += glyphUnicodeLength;
        text.canvasWidth += charWidth;
      }
      current.x += x * textHScale2;
      ctx.restore();
    }

    if (textSelection) {
      this.textLayer.appendText(text, font.loadedName, fontSize);
    }

    return text;
  }

  showSpacedText(arr: (number | string)[]): void {
    let ctx:PDFContext = this.ctx;
    let current:CanvasExtraState = this.current;
    let font: Font = current.font;
    let fontSize:number = current.fontSize;
    let textHScale:number = current.textHScale;

    if (!font.coded) {
      textHScale *= (current.fontMatrix || [1])[0];
    }

    let arrLength:number = arr.length;
    let text:BidiText = new BidiText('', 0, 0, {} as Geometry );
    let textSelection:boolean = !!this.textLayer;

    if (textSelection) {
      ctx.save();

      if (font.coded) {
        ctx.transform(
          current.textMatrix[0], current.textMatrix[1],
          current.textMatrix[Number_2], current.textMatrix[Number_3],
          current.textMatrix[Number_4], current.textMatrix[Number_5])
        ctx.scale(1, -1);
        ctx.translate(current.x, -1 * current.y);
        ctx.scale(textHScale, 1);
      } else {
        this.applyTextTransforms();
      }
      text.geom = this.getTextGeometry();
      ctx.restore();
    }

    for (let i = 0; i < arrLength; ++i) {
      const e:string | number = arr[i];

      if (isNum(e)) {
        const spacingLength: number = -e * Number_ooo1 * fontSize * textHScale;
        current.x += spacingLength;

        if (textSelection) {
          text.canvasWidth += spacingLength;
          if (e < 0 && text.geom.spaceWidth > 0) {
            const numFakeSpaces: number = Math.round(-e / text.geom.spaceWidth);
            if (numFakeSpaces > 0) {
              text.str += '\u00A0';
              text.length += 1;
            }
          }
        }
      } else if (typeof e === 'string') {
        const shownText:BidiText = this.showText(e, true);
        if (textSelection) {
          if (shownText.str === ' ') {
            text.str += '\u00A0';
          } else {
            text.str += shownText.str;
          }

          text.canvasWidth += shownText.canvasWidth;
          text.length += shownText.length;
        }
      } else {
        malformed(`TJ array element ${e} is not string or num`);
      }
    }

    if (textSelection) {
      this.textLayer.appendText(text, font.loadedName, fontSize);
    }
  }

  nextLineShowText(text: string): void {
    this.nextLine();
    this.showText(text);
  }

  nextLineSetSpacingShowText(wordSpacing: number, charSpacing: number, text: string): void {
    this.setWordSpacing(wordSpacing);
    this.setCharSpacing(charSpacing);
    this.nextLineShowText(text);
  }

  setCharWidth(xWidth: number, yWidth: number): void {

  }

  setCharWidthAndBounds(xWidth: number, yWidth: number, llx: number, lly: number, urx: number, ury: number): void {
    this.rectangle(llx, lly, urx - llx, ury - lly);
    this.clip();
    this.endPath();
  }

  setStrokeColorSpace(raw: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name) {
    return this.current.strokeColorSpace = ColorSpace.fromIR(raw) as DeviceGrayCS | PatternCS;
  }

  setFillColorSpace(raw: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name) {
    this.current.fillColorSpace = ColorSpace.fromIR(raw) as DeviceGrayCS | PatternCS;
  }

  setStrokeColor(...args: number[]): void {
    const cs:DeviceGrayCS = this.current.strokeColorSpace as DeviceGrayCS;
    const rgbColor:number[] = cs.getRgb(args);
    const color:string = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[Number_2]);
    this.ctx.strokeStyle = color;
    this.current.strokeColor = color;
  }

  getColorN_Pattern(IR: number[]|string[], cs: PatternCS): TilingPattern | Pattern {
    let pattern: TilingPattern | Pattern;
    if (IR[0] == 'TilingPattern') {
      const args: string | number = IR[1];
      const base:LabCS = cs.base;
      let color: number[] | null = new Array<number>();
      if (base !== null && base !== undefined) {
        const baseComps:number = base.numComps;
        for (let i = 0; i < baseComps; ++i) {
          color.push(args[i]);
        }
        color = base.getRgb(color);
      }
      pattern = new TilingPattern(IR, color, this.ctx, this.objs);
    } else if (IR[0] == 'RadialAxial' || IR[0] == 'Dummy') {
      pattern = Pattern.shadingFromIR(IR) as Pattern | TilingPattern;
    } else {
      error(`Unknown IR type ${IR[0]}`);
    }
    return pattern
  }

  setStrokeColorN(args: number[]): void {
    const cs:PatternCS = this.current.strokeColorSpace as PatternCS;
    if (cs.name == 'Pattern') {
      this.current.strokeColor = this.getColorN_Pattern(args, cs) as Pattern;
    } else {
      this.setStrokeColor(...args);
    }
  }

  setFillColor(args: number[]): void {
    const cs:DeviceGrayCS = this.current.fillColorSpace as DeviceGrayCS;
    const rgbColor:number[] = cs.getRgb(args);
    const color:string = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[Number_2]);
    this.ctx.fillStyle = color;
    this.current.fillColor = color;
  }

  setFillColorN(args: number[]): void {
    const cs:PatternCS = this.current.fillColorSpace as PatternCS;
    if (cs.name == 'Pattern') {
      this.current.fillColor = this.getColorN_Pattern(args, cs) as Pattern;
    } else {
      this.setFillColor(args);
    }
  }

  setStrokeGray(gray: number): void {
    if (!(this.current.strokeColorSpace instanceof DeviceGrayCS)) {
      this.current.strokeColorSpace = new DeviceGrayCS();
    }
    const color:string = Util.makeCssRgb(gray, gray, gray);
    this.ctx.strokeStyle = color;
    this.current.strokeColor = color;
  }

  setFillGray(gray: number): void {
    if (!(this.current.fillColorSpace instanceof DeviceGrayCS)) {
      this.current.fillColorSpace = new DeviceGrayCS();
    }
    const color:string = Util.makeCssRgb(gray, gray, gray);
    this.ctx.fillStyle = color;
    this.current.fillColor = color;
  }

  setStrokeRGBColor(r: number, g: number, b: number): void {
    if (!(this.current.strokeColorSpace instanceof DeviceRgbCS)) {
      this.current.strokeColorSpace = new DeviceRgbCS();
    }
    const color:string = Util.makeCssRgb(r, g, b);
    this.ctx.strokeStyle = color;
    this.current.strokeColor = color;
  }

  setFillRGBColor(r: number, g: number, b: number): void {
    if (!(this.current.fillColorSpace instanceof DeviceRgbCS)){
      this.current.fillColorSpace = new DeviceRgbCS();
    }
    const color:string = Util.makeCssRgb(r, g, b);
    this.ctx.fillStyle = color;
    this.current.fillColor = color;
  }

  setStrokeCMYKColor(c: number, m: number, y: number, k: number): void {
    if (!(this.current.strokeColorSpace instanceof DeviceCmykCS)){
      this.current.strokeColorSpace = new DeviceCmykCS();
    }
    const color:string = Util.makeCssCmyk(c, m, y, k);
    this.ctx.strokeStyle = color;
    this.current.strokeColor = color;
  }

  setFillCMYKColor(c: number, m: number, y: number, k: number): void {
    if (!(this.current.fillColorSpace instanceof DeviceCmykCS)){
      this.current.fillColorSpace = new DeviceCmykCS();
    }
    const color:string = Util.makeCssCmyk(c, m, y, k);
    this.ctx.fillStyle = color;
    this.current.fillColor = color;
  }

  shadingFill(patternIR: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): void {
    const ctx:PDFContext = this.ctx;

    this.save();
    const pattern:Pattern = Pattern.shadingFromIR(patternIR as (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[]) as Pattern;
    ctx.fillStyle = pattern.getPattern(ctx);
    const inv = ctx.mozCurrentTransformInverse;
    if (inv !== null && inv !== undefined) {
      const canvas:Canvas = ctx.canvas;
      const width:number = canvas.width;
      const height:number = canvas.height;

      const bl:number[] = Util.applyTransform([0, 0], inv);
      const br:number[] = Util.applyTransform([0, height], inv);
      const ul:number[] = Util.applyTransform([width, 0], inv);
      const ur:number[] = Util.applyTransform([width, height], inv);

      const x0:number = Math.min(bl[0], br[0], ul[0], ur[0]);
      const y0:number = Math.min(bl[1], br[1], ul[1], ur[1]);
      const x1:number = Math.max(bl[0], br[0], ul[0], ur[0]);
      const y1:number = Math.max(bl[1], br[1], ul[1], ur[1]);

      this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
    } else {
      this.ctx.fillRect(-Number_1e10, -Number_1e10, Number_2e10, Number_2e10);
    }

    this.restore();
  }

  beginInlineImage(): void {
    error('Should not call beginInlineImage');
  }

  beginImageData(): void {
    error('Should not call beginImageData');
  }

  paintFormXObjectBegin(matrix: number[] | null, bbox: number[] | null): void {
    this.save();

    if (matrix && isArray(matrix) && matrix.length === Number_6) {
      this.transform(matrix[0], matrix[1], matrix[Number_2], matrix[Number_3], matrix[Number_4], matrix[Number_5]);
    }

    if (bbox && isArray(bbox) && bbox.length === Number_4) {
      const width:number = bbox[Number_2] - bbox[0];
      const height:number = bbox[Number_3] - bbox[1];
      this.rectangle(bbox[0], bbox[1], width, height);
      this.clip();
      this.endPath();
    }
  }

  paintFormXObjectEnd(): void {
    this.restore();
  }

  paintJpegXObject(objId: string, w: number, h: number): void {
    const domImage:Image = this.objs.get(objId) as Image;
    if (domImage === null || domImage === undefined) {
      error("Dependent image isn't ready yet");
    }
    this.save();
    this.ctx.scale(1 / w, -1 / h);
    this.ctx.drawImageFunc(domImage, 0, 0, domImage.width, domImage.height, 0, -h, w, h);
    this.restore();
  }

  paintImageMaskXObject(imgArray: number[], inverseDecode: boolean, width: number, height: number): void {
    let applyStencilMask = (buffer: Uint8Array, inverseDecode: boolean): void => {
      let imgArrayPos:number = 0;
      let i:number;
      let j:number;
      let mask:number;
      let buf:number;
      let bufferPos:number = Number_3;

      while (i < height) {
        mask = 0;
        j = 0;
        while (j < width) {
          if (mask == 0) {
            buf = imgArray[imgArrayPos];
            imgArrayPos += 1;
            mask =Number_128;
          }
          if (((buf & mask) == 0) == inverseDecode ) {
            buffer[bufferPos] = 0;
          }
          bufferPos += Number_4;
          mask >>= 1;
          j += 1;
        }
        i += 1;
      }
    }

    this.save();

    const ctx:PDFContext = this.ctx;
    const w:number = width;
    const h:number = height;
    ctx.scale(1 / w, -1 / h);

    const tmpCanvas:Canvas = createScratchCanvas(w, h);
    const tmpCtx:PDFContext = tmpCanvas.getContext('2d');

    const fillColor: Pattern = this.current.fillColor as Pattern;
    if (fillColor !== null && fillColor !== undefined) {
      tmpCtx.fillStyle = fillColor.getPattern(tmpCtx);
    } else  {
      tmpCtx.fillStyle = fillColor;
    }
    tmpCtx.fillRect(0, 0, w, h);

    const imgData:Image = tmpCtx.getImageData(0, 0, w, h);
    const pixels:Uint8Array = imgData.data;

    applyStencilMask(pixels, inverseDecode);

    tmpCtx.putImageData(imgData, 0, 0);
    ctx.drawImage(tmpCanvas, 0, -h);
    this.restore();
  }

  paintImageXObject(objId: string): void {
    const imgData:Image = this.objs.get(objId) as Image;
    if (imgData === null || imgData === undefined){
      error("Dependent image isn't ready yet");
    }
    this.save();
    const ctx:PDFContext = this.ctx;
    const w:number = imgData.width;
    const h:number = imgData.height;
    ctx.scale(1 / w, -1 / h);

    const tmpCanvas:Canvas = createScratchCanvas(w, h);
    const tmpCtx:PDFContext = tmpCanvas.getContext('2d');
    this.putBinaryImageData(tmpCtx, imgData, w, h);

    ctx.drawImage(tmpCanvas, 0, -h);
    this.restore();
  }

  putBinaryImageData(ctx: PDFContext, imgData: Image,width:number,height:number) {
    let tmpImgData:Image = ctx.getImageData(0, 0, width, height);
    let tmpImgDataPixels:Uint8Array = tmpImgData.data;
    let len:number = tmpImgDataPixels.length;
    while (len !== 0) {
      len -= 1;
      tmpImgDataPixels[len] = imgData.data[len];
    }
    ctx.putImageData(tmpImgData, 0, 0);
  }

  markPoint(tag: string): void {
    TODO('Marked content');
  }

  markPointProps(tag: string, properties: string): void {
    TODO('Marked content');
  }

  beginMarkedContent(tag: string): void {
    TODO('Marked content');
  }

  beginMarkedContentProps(tag: string, properties: string[]): void {
    TODO('Marked content');
  }

  endMarkedContent(): void {
    TODO('Marked content');
  }

  beginCompat(): void {
    TODO('ignore undefined operators (should we do that anyway?)');
  }

  endCompat(): void {
    TODO('stop ignoring undefined operators');
  }

  consumePath(): void {
    if (this.pendingClip !== null && this.pendingClip !== undefined) {
      let savedFillRule: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = null;
      if (this.pendingClip === this.EO_CLIP) {
        savedFillRule = this.setEOFillRule();
      }
      this.ctx.clip();

      this.pendingClip = null;
      if (savedFillRule !== null) {
        this.restoreFillRule(savedFillRule as string);
      }
    }
    this.ctx.beginPath();
  }

  setEOFillRule(): string {
    const savedFillRule:string = this.ctx.mozFillRule;
    this.ctx.mozFillRule = 'evenodd';
    return savedFillRule;
  }

  restoreFillRule(rule: string): void {
    this.ctx.mozFillRule = rule;
  }

  getSinglePixelWidth(scale: number = 0.0): number {
    const inverse:number[] = this.ctx.mozCurrentTransformInverse;
    return Math.abs(inverse[0] + inverse[Number_2]);
  }

  putBinaryImageDataFunc(ctx: PDFContext,imgData: Image) {
    ctx.putImageData(imgData, 0, 0)
  }
}

if (isWorker === null || isWorker === undefined) {
  let canvas: Canvas = PdfJS_window.document.createElement('canvas') as Canvas;
  canvas.width = 1;
  canvas.height = 1;
  let ctx:PDFContext = canvas.getContext('2d');
  ctx.putImageData({
    width: 1,
    height: 1,
    data: new Uint8Array(Number_3).fill(0)
  },0 ,0);
}

class Name {
  name: string
  constructor(name: string) {
    this.name = name
  }
}

class Cmd {
  cmd: string;
  static cmdCache: Record<string, Cmd> = {};

  constructor(cmd: string) {
    this.cmd = cmd;
  }

  static get(cmd: string): Cmd {
    let cmdValue:Cmd = Cmd.cmdCache[cmd];
    if (cmdValue !== null && cmdValue !== undefined) {
      return cmdValue;
    }
    let newCmd: Cmd = new Cmd(cmd);
    Cmd.cmdCache[cmd] = newCmd;
    return newCmd;
  }
}

class Ref {
  num: number;
  gen: number;
  constructor(num: number, gen: number) {
    this.num = num;
    this.gen = gen;
  }
}

class Dict {
  xref?: XRef;
  map: Map<string, object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name> = new Map();
  loadedName: string;
  translated?: FontTranslationResult;

  constructor(xref?: XRef) {
    this.map = new Map();
    this.xref = xref;
  }

  assignXref(newXref: XRef): void {
    this.xref = newXref;
  }

  get(key1: string, key2: string = null, key3: string = null): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    let value: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
    let keyValue = this.map.get(key1) ?? null;
    if (keyValue !== null) {
      value = this.xref ? this.xref.fetchIfRef(keyValue) : keyValue;
      return value;
    }
    let key2Value = this.map.get(key2) ?? null;
    if (key2Value !== null) {
      value = this.xref ? this.xref.fetchIfRef(key2Value) : key2Value;
      return value;
    }
    let key3Value = this.map.get(key3) ?? null;
    if (key3Value !== null) {
      value = this.xref ? this.xref.fetchIfRef(key3Value) : key3Value;
      return value;
    }
    return null;
  }

  getRaw(key: string) {
    return this.map.get(key);
  }

  getAll(): Map<string, string|object|number|boolean> {
    let all:Map<string, string|object|number|boolean> = new Map<string, string>();
    for (let key of this.map.keys()) {
      let obj: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = this.get(key);
      let value: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = this.map.get(key);
      all.set(key,obj instanceof Dict ? (obj as Dict).getAll() : value);
    }
    return all;
  }

  set(key: string, value: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): void {
    this.map.set(key,value);
  }

  has(key: string): boolean {
    return (this.map.get(key)!== null && this.map.get(key) !== undefined)
  }

  forEach(callback: (key: string, value: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name) => void): void {
    for (let key of this.map) {
      callback(key[0], this.get(key[0]));
    }
  }
}

class RefSet {
  private dict = new Map<string, Ref>()
  constructor() {
    this.dict = new Map<string, Ref>()
  }

  has(ref: Ref): boolean {
    return !!this.dict[`R${ref.num}.${ref.gen}`];
  }

  put(ref: Ref): void {
    this.dict[`R${ref.num}.${ref.gen}`] = ref;
  }
}

class DocumentOutlineQueue {
  obj: Ref;
  parent: DocumentOutlineItem;
  constructor(obj: Ref, parent: DocumentOutlineItem){
    this.obj = obj
    this.parent = parent
  }
}

class DocumentOutlineItem {
  dest: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
  title?: string;
  color?: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[];
  count?: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
  bold: boolean;
  italic: boolean;
  items: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[];
  constructor(dest: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = null, title: string = null, color: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = null, count?: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name, bold: boolean = false, italic: boolean = false, items:(object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = new Array()) {
    this.dest = dest
    this.title = title
    this.color = color
    this.count = count
    this.bold = bold
    this.italic = italic
    this.items = items;
  }
}

class Catalog {
  private xref: XRef;
  catDict: Dict;
  private pageCache: Page[];
  constructor(xref: XRef) {
    this.xref = xref;
    let obj: Dict = xref.getCatalogObj();
    assertWellFormed(isDict(obj), 'catalog object is not a dictionary');
    this.catDict = obj;
  }

  get metadata(): string | null {
    let stream: Stream | null = this.catDict.get('Metadata') as Stream;
    let metadata: string | null = null;
    if ((stream !== null && stream !== undefined) && isDict(stream.dict)){
      const type: Name = stream.dict.get('Type') as Name;
      const subtype: Name = stream.dict.get('Subtype') as Name;

      if (type.name === 'Metadata' && subtype.name === 'XML') {
        metadata = stringToPDFString(bytesToString(stream.getBytes()));
      }
    }
    return metadata;
  }

  get toplevelPagesDict(): Dict {
    const  pagesObj: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = this.catDict.get('Pages');
    assertWellFormed(isDict(pagesObj), 'invalid top-level pages dictionary');
    return pagesObj as Dict;
  }

  get documentOutline(): DocumentOutlineItem[] | null {
    const xref:XRef = this.xref;
    let obj: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
    obj = this.catDict.get('Outlines');
    const root: DocumentOutlineItem = new DocumentOutlineItem();

    if (isDict(obj)) {
      obj = (obj as Dict).getRaw('First');
      let processed: RefSet = new RefSet();
      if (isRef(obj)) {
        let queue: DocumentOutlineQueue[] = [new DocumentOutlineQueue((obj as Ref), root)];
        processed.put(obj as Ref);
        while (queue.length > 0) {
          const i:DocumentOutlineQueue = queue.shift()!;
          const outlineDict:Dict = xref.fetchIfRef(i.obj) as Dict;
          if (outlineDict === null) {
            continue;
          }
          if (!outlineDict.has('Title')){
            error('Invalid outline item');
          }
          let dest: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = outlineDict.get('A');
          if (dest !== null && dest !== undefined) {
            dest = (dest as Dict).get('D');
          } else if (outlineDict.has('Dest')) {
            dest = outlineDict.getRaw('Dest');
            if (isName(dest)) {
              dest = (dest as Name).name;
            }
          }
          const title: string = outlineDict.get('Title') as string;
          const outlineItem: DocumentOutlineItem = new DocumentOutlineItem(
            dest,
            stringToPDFString(title),
            outlineDict.get('C') as number[] || [0, 0, 0],
            outlineDict.get('Count'),
            !!((outlineDict.get('F') as number) &Number_2),
            !!((outlineDict.get('F') as number) & 1),
            []);

          i.parent.items.push(outlineItem);
          let obj = outlineDict.getRaw('First');
          if (isRef(obj) && !processed.has(obj as Ref)) {
            queue.push( new DocumentOutlineQueue(obj as Ref, outlineItem));
            processed.put(obj as Ref);
          }
          obj = outlineDict.getRaw('Next');
          if (isRef(obj) && !processed.has((obj as Ref))) {
            queue.push(new DocumentOutlineQueue(obj as Ref,i.parent));
            processed.put(obj as Ref);
          }
        }
      }
    }
    obj = root.items.length > 0 ? root.items : null;
    return obj as DocumentOutlineItem[];
  }

  get numPages(): number {
    let obj: number = this.toplevelPagesDict!.get('Count') as number;
    assertWellFormed(isInt(obj), 'page count in top level pages object is not an integer');
    return  obj;
  }

  traverseKids(pagesDict: Dict): void {
    const pageCache:Page[] = this.pageCache;
    const kids:Ref[] = pagesDict.get('Kids') as Ref[];
    assertWellFormed(isArray(kids), 'page dictionary kids object is not an array');
    for (let i = 0; i <  kids.length; ++i) {
      const kid: Ref = kids[i] as Ref;
      assertWellFormed(isRef(kid), 'page dictionary kid is not a reference');
      const obj = this.xref.fetch(kid);
      if (isDict(obj, 'Page') || (isDict(obj) && !(obj as Dict).has('Kids'))) {
        pageCache.push(new Page(this.xref, pageCache.length, (obj as Dict), kid));
      } else {
        assertWellFormed(isDict(obj), 'page dictionary kid reference points to wrong type of object');
        this.traverseKids(obj as Dict);
      }
    }
    this.pageCache = pageCache;
  }

  get destinations(): Map<string,string> {
    let fetchDestination = (dest: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): string | null => {
      return isDict(dest) ? (dest as Dict).get('D') as string : dest as string;
    }
    const xref = this.xref;
    let dests: Map<string,string> = new Map();
    let nameTreeRef: Ref | null;
    let nameDictionaryRef: Dict | null;
    let obj: Dict = this.catDict.get('Names') as Dict;
    if (obj !== null && obj !== undefined) {
      nameTreeRef = obj.getRaw('Dests') as Ref;
    } else if (this.catDict.has('Dests')) {
      nameDictionaryRef = this.catDict.get('Dests') as Dict
    }
    if (nameDictionaryRef !== null && nameDictionaryRef !== undefined) {
      obj = nameDictionaryRef;
      obj.forEach(function catalogForEach(key, value) {
        if (value === null || value === undefined) {
          return;
        }
        dests[key] = fetchDestination(value);
      });
    }
    if (nameTreeRef !== null && nameTreeRef !== undefined) {
      const processed:RefSet = new RefSet();
      processed.put(nameTreeRef);
      const queue:Ref[] = [nameTreeRef];
      while (queue.length > 0) {
        obj = xref.fetch(queue.shift()!)! as Dict;
        if (obj.has('Kids')) {
          const kids: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = obj.get('Kids');
          for (let i = 0; i < (kids as Ref[]).length; i++) {
            const kid: Ref = kids[i] as Ref;
            if (processed.has(kid)) {
              error('invalid destinations');
            }
            queue.push(kid);
            processed.put(kid);
          }
          continue;
        }
        const names: string[] = obj.get('Names') as string[];
        for (let i: number = 0; i < names.length; i +=Number_2) {
          dests.set(names[i], fetchDestination(xref.fetchIfRef(names[i + 1])));
        }
      }
    }
    return dests;
  }

  getPage(n: number): Page {
    let pageCache:Page[] = this.pageCache;
    if (pageCache === null || pageCache === undefined){
      pageCache = new Array<Page>();
      this.pageCache = new Array<Page>();
      this.traverseKids(this.toplevelPagesDict);
    }
    return this.pageCache[n - 1];
  }
}

class XRef {
  private stream: Stream;
  entries?: XRefEntry[] = new Array<XRefEntry>();
  private xrefstms?: number[] = new Array<number>();
  trailer: Dict;
  private cache: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = new Array<object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>();
  private encrypt?: CipherTransformFactory;
  private root?: Dict;
  constructor(stream: Stream, startXRef: number, mainXRefEntriesOffset: number) {
    this.stream = stream;
    this.entries = new Array<XRefEntry>();
    this.xrefstms = new Array<number>();
    let  trailerDict: Dict = this.readXRef(startXRef);
    trailerDict.assignXref(this);
    this.trailer = trailerDict;
    this.cache = new Array<object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>();

    const encrypt:Dict = trailerDict.get('Encrypt') as Dict;
    if (encrypt !== null && encrypt !== undefined) {
      const fileId:string[] = trailerDict.get('ID') as string[];
      this.encrypt = new CipherTransformFactory(encrypt, fileId[0]);
    }

    let root: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = trailerDict.get('Root')
    if (root!== null && root !== undefined){
      this.root = root as Dict;
    } else  {
      this.root = null;
      error('Invalid root reference');
    }
  }

  private readXRefTable(parser: Parser): Dict {
    let obj = parser.getObj();
    while (!isCmd((obj), 'trailer')) {
      const first:number = obj as number;
      const count:number = parser.getObj() as number;

      if (!isInt(first) || !isInt(count)) {
        error('Invalid XRef table: wrong types in subsection header');
      }

      for (let i = 0; i < count; i++) {
        const entry: XRefEntry = new XRefEntry();
        entry.offset = parser.getObj() as number;
        entry.gen = parser.getObj() as number;
        const type: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = parser.getObj();

        if (isCmd(type, 'f')) {
          entry.free = true;
        } else if (isCmd(type, 'n')) {
          entry.uncompressed = true;
        }
        if (!isInt(entry.offset) || !isInt(entry.gen) ||
          !(entry.free === true || entry.uncompressed === true)) {
          error(`Invalid entry in XRef subsection:  ${first} ,  ${count}`);
        }

        this.entries[i + (first as number)] = entry;

      }
      obj = parser.getObj();
    }

    if (this.entries[0] && !this.entries[0].free){
      error('Invalid XRef table: unexpected first object');
    }

    if (!isCmd(obj, 'trailer')){
      error('Invalid XRef table: could not find trailer dictionary');
    }

    const dict:Dict | null = parser.getObj() as Dict;
    if (!isDict(dict)){
      error('Invalid XRef table: could not parse trailer dictionary');
    }
    return dict as Dict;
  }

  private readXRefStream(stream: Stream): Dict {
    const streamParameters:Dict = stream.parameters;
    let byteWidths:number[] = streamParameters.get('W') as number[];
    let range: number[] = streamParameters.get('Index') as number[] ??  [0, streamParameters.get('Size') as number];
    while (range.length > 0) {
      let first: number = range[0];
      let n: number = range[1];
      if (!isInt(first) || !isInt(n)){
        error(`Invalid XRef range fields: ${first} , ${n}`);
      }
      let typeFieldWidth:number = byteWidths[0];
      let offsetFieldWidth:number = byteWidths[1];
      let generationFieldWidth:number = byteWidths[Number_2];
      if (!isInt(typeFieldWidth) || !isInt(offsetFieldWidth) || !isInt(generationFieldWidth)) {
        error(`Invalid XRef entry fields length: ${first}, ${n}`);
      }
      for (let i: number = 0; i < n; ++i) {
        let type:number = 0;
        let offset:number = 0;
        let generation:number = 0;
        for (let j: number = 0; j < typeFieldWidth; ++j){
          type = (type << Number_8) | stream.getByte();
        }
        if (typeFieldWidth == 0) {
          type = 1;
        }
        for (let j: number = 0; j < offsetFieldWidth; ++j){
          offset = (offset << Number_8) | stream.getByte();
        }
        for (let j: number = 0; j < generationFieldWidth; ++j){
          generation = (generation << Number_8) | stream.getByte();
        }
        const entry: XRefEntry = new XRefEntry();
        entry.offset = offset;
        entry.gen = generation;
        switch (type) {
          case 0:
            entry.free = true;
            break;
          case 1:
            entry.uncompressed = true;
            break;
          case Number_2:
            break;
          default:
            error(`Invalid XRef entry type: ${type}`);
        }
        if (!this.entries[first + i]){
          this.entries[first + i] = entry;
        }
      }
      range.splice(0, Number_2);
    }
    return streamParameters;
  }

  indexObjects(): Dict {
    const readToken = (data: Uint8Array, offset: number): string => {
      let token:string = '';
      offset = offset;
      let ch:number = data[offset];
      while (ch !== Number_13 && ch !== Number_10) {
        if (offset >= data.length) {
          offset += 1;
          break;
        }
        token += String.fromCharCode(ch);
        ch = data[offset];
      }
      return token;
    };

    const skipUntil = (data: Uint8Array, offset: number, what: Uint8Array): number => {
      const length:number = what.length;
      const dataLength:number = data.length;
      let skipped:number = 0;
      while (offset < dataLength) {
        let i:number = 0;
        while (i < length && data[offset + i] === what[i]) {
          i += 1;
        }
        if (i >= length) {
          break;
        }
        offset += 1;
        skipped += 1;
      }
      return skipped;
    };

    const trailerBytes:Uint8Array = new Uint8Array([116, 114, 97, 105, 108, 101, 114]);
    const startxrefBytes:Uint8Array = new Uint8Array([115, 116, 97, 114, 116, 120, 114, 101, 102]);
    const endobjBytes:Uint8Array = new Uint8Array ([101, 110, 100, 111, 98, 106]);
    const xrefBytes:Uint8Array = new Uint8Array ([47, 88, 82, 101, 102]);

    const stream: Stream = this.stream;
    stream.pos = 0;
    const buffer:Uint8Array = stream.getBytes();
    let position: number = stream.start;
    const length:number = buffer.length;
    const trailers: number[] = new Array<number>();
    const xrefStms: number[] = new Array<number>();

    while (position < length) {
      let ch:number = buffer[position];
      if (ch === Number_32 || ch === Number_9 || ch === Number_13 || ch === Number_10) {
        position += 1;
        continue;
      }
      if (ch === Number_37) {
        while (buffer[position] !== Number_13 && buffer[position] !== Number_10){
          position += 1;
        }
        continue;
      }

      const token = readToken(buffer, position);
      let m: RegExpExecArray;
      let regex: RegExp = RegExp('^(\d+)\s+(\d+)\s+obj\b');
      if (token === 'xref') {
        position += skipUntil(buffer, position, trailerBytes);
        trailers.push(position);
        position += skipUntil(buffer, position, startxrefBytes);
      } else if (regex.exec(token).length !== 0) {
        m = regex.exec(token);
        this.entries[Number.parseInt(m[1])] = {
          offset: position,
          gen: Number.parseInt(m[Number_2]) | 0,
          uncompressed: true,
        };

        const contentLength:number = skipUntil(buffer, position, endobjBytes) + Number_7;
        const content:Uint8Array = buffer.subarray(position, position + contentLength);
        const xrefTagOffset:number = skipUntil(content, 0, xrefBytes);
        if (xrefTagOffset < contentLength && content[xrefTagOffset + Number_5] < Number_64) {
          xrefStms.push(position);
          this.xrefstms[position] = 1;
        }
        position += contentLength;
      } else {
        position += token.length + 1;
      }
    }

    for (let i = 0, ii = xrefStms.length; i < ii; ++i) {
      this.readXRef(xrefStms[i], true);
    }

    let dict:Dict;
    for (let i = 0; i < trailers.length; ++i) {
      stream.pos = trailers[i];
      const parser:Parser = new Parser(new Lexer(stream), true, null);
      const obj: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = parser.getObj();
      if (!isCmd(obj, 'trailer')) {
        continue;
      }
      dict = parser.getObj() as Dict;
      if (!isDict(dict)) {
        continue;
      }
      if (dict.has('ID')) {
        return dict;
      }
    }
    if (dict !== null && dict !== undefined) {
      return dict;
    }
    error('Invalid PDF structure');
    return new Dict(null)
  }

  readXRef(startXRef: number, recoveryMode?: boolean): Dict {
    const stream:Stream = this.stream;
    stream.pos = startXRef;

    try {
      const parser:Parser = new Parser(new Lexer(stream), true, null);
      let obj = parser.getObj();
      let dict:Dict;
      if (isCmd(obj, 'xref')) {
        dict = this.readXRefTable(parser);
        obj = dict.get('XRefStm');
        if (isInt(obj)) {
          const pos: number = obj as number;
          if (!(this.xrefstms.includes(pos))) {
            this.xrefstms[pos] = 1;
            this.readXRef(pos);
          }
        }
      } else if (isInt(obj)) {
        if (!isInt(parser.getObj()) || !isCmd(parser.getObj(), 'obj') || !isStream((obj = parser.getObj()))) {
          error('Invalid XRef stream');
        }
        dict = this.readXRefStream(obj as Stream);
        if (dict === null || dict === undefined) {
          error('Failed to read XRef stream');
        }
      }
      obj = dict.get('Prev');
      if (isInt(obj)) {
        this.readXRef((obj as number), recoveryMode);
      }
      else if (isRef(obj)) {
        this.readXRef((obj as Ref).num, recoveryMode);
      }
      return dict;
    } catch (e) {
      log('(while reading XRef): ' + e);
    }
    if (recoveryMode) {
      return;
    }
    warn('Indexing all PDF objects');
    return this.indexObjects();
  }

  getEntry(i: number): XRefEntry {
    const e:XRefEntry = this.entries[i];
    if (e === null) {
      return null;
    }
    return e.free ? null : e;
  }

  fetchIfRef(obj: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    if (!isRef(obj)) {
      return obj;
    }
    return this.fetch(obj as Ref);
  }

  fetch(ref: Ref, suppressEncryption?: boolean): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    assertWellFormed(isRef(ref), 'ref object is not a reference');
    let num:number = ref.num;
    if (num in (this.cache as number[])) {
      return this.cache[num];
    }

    let e:XRefEntry | Stream | JpegStream = this.getEntry(num);
    if (e === null || e === undefined) {
      this.cache[num] = null;
      return e;
    }

    const gen:number = ref.gen;
    let stream:Stream;
    let parser:Parser;
    if (e.uncompressed) {
      if (e.gen !== gen) {
        error('inconsistent generation in XRef');
      }
      stream = this.stream.makeSubStream(e.offset);
      parser = new Parser(new Lexer(stream), true, this);
      const obj1:number = parser.getObj() as number;
      const obj2:number = parser.getObj() as number;
      const obj3:Cmd = parser.getObj() as Cmd;
      if (!isInt(obj1) || obj1 !== num || !isInt(obj2) || obj2 !== gen || !isCmd(obj3)) {
        error('bad XRef entry');
      }
      if (!isCmd(obj3, 'obj')) {
        if (obj3.cmd.indexOf('obj') === 0) {
          num = Number.parseInt(obj3.cmd.substring(Number_3), Number_10);
          if (isNum(num)) {
            return num;
          }
        }
        error('bad XRef entry');
      }
      if (this.encrypt && !suppressEncryption) {
        try {
          e = parser.getObj(this.encrypt.createCipherTransform(num, gen)) as XRefEntry | Stream | JpegStream;
        } catch (ex) {
          return this.fetch(ref, true);
        }
      } else {
        e = parser.getObj() as XRefEntry | Stream | JpegStream;
      }
      if ( !isStream(e) ||e instanceof JpegStream) {
        this.cache[num] = e;
      }
      return e;
    }

    stream = this.fetch(new Ref(e.offset, 0)) as Stream;
    if (!isStream(stream)) {
      error('bad ObjStm stream');
    }
    const first:number = stream.parameters.get('First') as number;
    const n:number = stream.parameters.get('N') as number;
    if (!isInt(first) || !isInt(n)) {
      error('invalid first and n parameters for ObjStm stream');
    }
    parser = new Parser(new Lexer(stream), false, this);
    let entries: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = new Array<object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>();
    let nums:number[] = new Array<number>();
    for (let i:number = 0; i < n; ++i) {
      let num: number = parser.getObj() as number;
      if (!isInt(num)) {
        error(`Invalid object number in the ObjStm stream: ${num}`);
      }
      nums.push(num);
      const offset:number = parser.getObj() as number;
      if (!isInt(offset)) {
        error(`Invalid object offset in the ObjStm stream: ${offset}`);
      }
    }
    for (let i:number = 0; i < n; ++i) {
      entries.push(parser.getObj());
      this.cache[nums[i]] = entries[i];
    }
    e = entries[e.gen] as XRefEntry | Stream | JpegStream;
    if (e === null || e === undefined){
      error('bad XRef entry for compressed object');
    }
    return e;
  }

  getCatalogObj():Dict {
    return this.root;
  }
}

class PDFObjects {
  objs:Map<string, PDFPromise> = new Map<string, PDFPromise>()

  ensureObj(objId: string, data?: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): PDFPromise {
    if (this.objs.get(objId) !== null && this.objs.get(objId) !== undefined){
      return this.objs.get(objId);
    }else{
      let promise: PDFPromise = new PDFPromise(objId, data as Record<string, string>);
      this.objs.set(objId,promise);
      return promise;
    }
  }

  get(objId: string, callback: Function = null): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    if (callback !== null && callback !== undefined) {
      this.ensureObj(objId).then(callback);
      return null;
    }
    const obj:PDFPromise = this.objs.get(objId);
    if (!obj || !obj.isResolved) {
      error(`Requesting object that isn't resolved yet ${objId}`);
    }
    return obj.data;
  }

  resolve(objId: string, data: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): void {
    const objs:Map<string, PDFPromise> = this.objs;
    if (objs.get(objId) !== null && objs.get(objId) !== undefined){
      objs.get(objId).resolve(data);
    } else {
      this.ensureObj(objId, data);
    }
  }

  onData(objId: string, callback: Function): void {
    this.ensureObj(objId).onData(callback);
  }

  isResolved(objId: string): boolean {
    return this.objs.get(objId).isResolved ?? false
  }

  hasData(objId: string): boolean {
    return (this.objs.get(objId) === null || this.objs.get(objId) === undefined) ? false : this.objs[objId].hasData;
  }

  setData(objId: string, data: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): void {
    this.ensureObj(objId).data = data;
  }
}

class PDFFunction {
  static CONSTRUCT_SAMPLED = 0;
  static CONSTRUCT_INTERPOLATED = Number_2;
  static CONSTRUCT_STICHED = Number_3;
  static CONSTRUCT_POSTSCRIPT = Number_4;

  static getSampleArray(size: number[], outputSize: number, bps: number, str: StringStream): number[] {
    let length:number = 1;
    for (let i = 0; i < size.length; i++){
      length *= size[i];
    }
    length *= outputSize;

    const array: number[] = new Array<number>();
    let codeSize:number = 0;
    let codeBuf:number = 0;
    const sampleMul:number = 1.0 / (Math.pow(Number_2o, bps) - 1);
    const strBytes:Uint8Array = str.getBytes((length * bps + Number_7) / Number_8);
    let strIdx:number = 0;
    for (let i = 0; i < length; i++) {
      while (codeSize < bps) {
        codeBuf <<= Number_8;
        codeBuf |= strBytes[strIdx];
        codeSize += Number_8;
        strIdx += 1;
      }
      codeSize -= bps;
      array.push((codeBuf >> codeSize) * sampleMul);
      codeBuf &= (1 << codeSize) - 1;
    }
    return array;
  }

  static getIR(xref: XRef, fn: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    let dict:Dict = (fn as Dict).get("dict") as Dict;
    if (dict === null || dict === undefined){
      dict = fn as Dict;
    }
    const types: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = [this.constructSampled, null, this.constructInterpolated, this.constructStiched, this.constructPostScript];
    const typeNum:number = dict.get('FunctionType') as number;
    const typeFn:Function = types[typeNum] as Function;
    if (typeFn === null || typeFn === undefined){
      error('Unknown type of function');
    }
    return typeFn.call(this, fn, dict, xref);
  }

  static fromIR(IR: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    const type: number = IR[0];
    switch (type) {
      case PDFFunction.CONSTRUCT_SAMPLED:
        return this.constructSampledFromIR(IR);
      case PDFFunction.CONSTRUCT_INTERPOLATED:
        return this.constructInterpolatedFromIR(IR);
      case PDFFunction.CONSTRUCT_STICHED:
        return this.constructStichedFromIR(IR);
      case PDFFunction.CONSTRUCT_POSTSCRIPT:
        return this.constructPostScriptFromIR(IR as [][]);
      default:
        return this.constructPostScriptFromIR(IR as [][]);
    }
  }

  static parse(xref: XRef, fn: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    const IR = this.getIR(xref, fn);
    return this.fromIR(IR);
  }

  static constructSampled(str: StringStream, dict: Dict): (number | number[] | number[][])[] {
    let toMultiArray = (arr) => {
      let inputLength: number = arr.length;
      let outputLength:number = arr.length / Number_2;
      let out: number[][] = new Array<number[]>();
      let index:number = 0;
      for (let i = 0; i < inputLength; i += Number_2) {
        out.push(arr[i], arr[i + 1]);
        index += 1;
      }
      return out;
    }

    let domain:number[] = dict.get('Domain') as number[];
    let range:number[] = dict.get('Range') as number[];
    if ((domain === null || domain === undefined) ||(range === null || range === undefined)){
      error('No domain or range');
    }

    let inputSize:number = domain.length / Number_2;
    let outputSize:number = range.length / Number_2;

    let size:number[] = dict.get('Size') as number[];
    let bps:number = dict.get('BitsPerSample') as number;
    let order:number = dict.get('Order') as number;
    if (order === null || order === undefined){
      order = 1;
    }
    if (order !== 1){
      error(`'No support for cubic spline interpolation: ' ${order}`);
    }

    let encode: number[] = dict.get('Encode') as number[];
    if (encode === null || encode === undefined){
      encode = new Array();
      for (let i = 0; i < inputSize; ++i) {
        encode.push(0);
        encode.push(size[i] - 1);
      }
    }

    let decode = dict.get('Decode');
    if (decode === null || decode === undefined){
      decode = range;
    }

    let samples: number[] = this.getSampleArray(size, outputSize, bps, str);

    return [
      PDFFunction.CONSTRUCT_SAMPLED, inputSize,  toMultiArray(domain), toMultiArray(encode), toMultiArray(decode), samples, size,outputSize, Math.pow(Number_2, bps) - 1, toMultiArray(range)
    ];
  }

  static constructSampledFromIR(IR):(args: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name) => Float64Array {
    let interpolate = (x: number, xmin: number, xmax: number, ymin: number, ymax: number) => {
      return ymin + ((x - xmin) * ((ymax - ymin) / (xmax - xmin)));
    }
    return (args: number[]) => {
      let m:number = IR[1];
      let domain:number[][] = IR[Number_2];
      let encode:number[][] = IR[Number_3];
      let decode:number[][] = IR[Number_4];
      let samples:number[] = IR[Number_5];
      let size:number[] = IR[Number_6];
      let n:number = IR[Number_7];
      let mask:number = IR[Number_8];
      let range:number[][] = IR[Number_9];

      if (m !== args.length) {
        error(`'Incorrect number of arguments: ' ${args.length} ' !== ${m}`);
      }

      let x: number[] = args;
      let cubeVertices:number = 1 << m;
      let cubeN:Float64Array = new Float64Array(cubeVertices).fill(1);
      let cubeVertex:Uint32Array = new Uint32Array(cubeVertices).fill(0);
      let k:number = n;
      let pos:number = 1;
      for (let i = 0; i < m; ++i) {
        let domain_2i:number = domain[i][0];
        let domain_2i_1:number = domain[i][1];
        let xi:number = Math.min(Math.max(x[i], domain_2i), domain_2i_1);
        let e:number = interpolate(xi, domain_2i, domain_2i_1, encode[i][0], encode[i][1]);
        let size_i:number = size[i];
        e = Math.min(Math.max(e, 0), size_i - 1);

        let e0:number = e < size_i - 1 ? Math.floor(e) : e - 1;
        let n0:number = e0 + 1 - e;
        let n1:number = e - e0;
        let offset0:number = e0 * k;
        let offset1:number = offset0 + k;
        for (let j = 0; j < cubeVertices; j++) {
          if ((j & pos) !== 0) {
            cubeN[j] *= n1;
            cubeVertex[j] += offset1;
          } else {
            cubeN[j] *= n0;
            cubeVertex[j] += offset0;
          }
        }
        k *= size_i;
        pos <<= 1;
      }

      let y:Float64Array = new Float64Array(n).fill(0);
      for (let j = 0; j < n; ++j) {
        let rj = 0;
        for (let i = 0; i < cubeVertices; i++){
          rj += samples[cubeVertex[i] + j] * cubeN[i];
        }
        rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]);
        y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]);
      }
      return y;
    }
  }

  static constructInterpolated(str, dict) {
    let c0 :number[] = dict.get('C0') || [0];
    let c1:number[] = dict.get('C1') || [1];
    let n:number = dict.get('N');

    if (!isArray(c0) || !isArray(c1)) {
      error('Illegal dictionary for interpolated function');
    }
    let length: number = c0.length;
    let diff: number[] = new Array<number>();
    for (let i = 0; i < length; ++i){
      diff.push(c1[i] - c0[i]);
    }
    return [PDFFunction.CONSTRUCT_INTERPOLATED, c0, diff, n];
  }

  static constructInterpolatedFromIR(IR) {
    let c0:number[] = IR[1];
    let diff:number[] = IR[Number_2];
    let n:number = IR[Number_3];
    let length: number = diff.length;
    return (args) => {
      let x = n == 1 ? args[0] : Math.pow(args[0], n);
      let out: number[] = new Array<number>();
      for (let j = 0; j < length; ++j){
        out.push(c0[j] + (x * diff[j]));
      }
      return out;
    }
  }

  static constructStiched(fn, dict, xref) {
    let domain:number[] = dict.get('Domain');
    if (domain === null || domain === undefined){
      error('No domain');
    }
    let inputSize:number = domain.length / Number_2;
    if (inputSize !== 1){
      error('Bad domain for stiched function');
    }
    let fnRefs: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = dict.get('Functions');
    let fns: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] =  new Array<object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>();
    for (let i = 0, ii = fnRefs.length; i < ii; ++i){
      fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i])));
    }
    let bounds:number[] = dict.get('Bounds');
    let encode:number = dict.get('Encode');
    return [PDFFunction.CONSTRUCT_STICHED, domain, bounds, encode, fns];
  }

  static constructStichedFromIR(IR): (a:number[])=>number[] {
    let domain:number[] = IR[1];
    let bounds:number[]  = IR[Number_2];
    let encode:number[]  = IR[Number_3];
    let fnsIR: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[][] = IR[Number_4];
    let fns: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = new Array<object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>();
    for (let i = 0; i < fnsIR.length; i++) {
      fns.push(PDFFunction.fromIR(fnsIR[i]));
    }
    return (args: number[]): number[] => {
      let clip = (v: number, min: number, max: number) => {
        if (v > max){
          v = max;
        }
        else if (v < min){
          v = min;
        }
        return v;
      }
      let v:number = clip(args[0], domain[0], domain[1]);
      let i:number = 0
      for (let ii: number = bounds.length; i < ii; ++i) {
        if (v < bounds[i]){
          i = ii;
          break;
        }
      }
      let dmin:number = domain[0];
      if (i > 0){
        dmin = bounds[i - 1];
      }
      let dmax:number = domain[1];
      if (i < bounds.length){
        dmax = bounds[i];
      }
      let rmin:number = encode[Number_2 * i];
      let rmax:number = encode[Number_2 * i + 1];
      let v2:number = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin);
      return (fns[i] as (args:number[])=>number[])([v2]);
    }
  }

  static constructPostScript(fn, dict, xref) {
    let domain = dict.get('Domain');
    let range = dict.get('Range');
    if (domain === null || domain === undefined){
      error('No domain.');
    }
    if (range === null || range === undefined){
      error('No range.');
    }
    let lexer:PostScriptLexer = new PostScriptLexer(fn);
    let parser:PostScriptParser = new PostScriptParser(lexer);
    let code: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = parser.parse();
    return [PDFFunction.CONSTRUCT_POSTSCRIPT, domain, range, code];
  }

  static constructPostScriptFromIR(IR:[][]):(args:number[]) => number[] {
    let domain:number[] = IR[1];
    let range:number[] = IR[Number_2];
    let code:[] = IR[Number_3];
    let numOutputs:number = range.length / Number_2;
    let evaluator: PostScriptEvaluator = new PostScriptEvaluator(code);
    let cache:FunctionCache = new FunctionCache();
    return (args: number[]): number[] => {
      let initialStack: number[] = new Array<number>();
      for (let i = 0; i < (domain.length / Number_2); ++i) {
        initialStack.push(args[i]);
      }

      let key:string = initialStack.join('_');
      if (cache.has(key)){
        return cache.get(key) as number[];
      }
      let stack: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name= evaluator.execute(initialStack);
      let transformed: number[] = new Array<number>();
      for (let i = numOutputs - 1; i >= 0; --i) {
        let out:number = (stack as number[]).pop();
        let rangeIndex:number = Number_2 * i;
        if (out < range[rangeIndex]){
          out = range[rangeIndex];
        } else if (out > range[rangeIndex + 1]){
          out = range[rangeIndex + 1];
        }
        transformed[i] = out;
      }
      cache.set(key, transformed);
      return transformed;
    };
  }
}

class FunctionCache {
  private cache = new Map<string, object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>()
  private total: number;
  MAX_CACHE_SIZE: number = Number_1024;

  constructor() {
    this.cache = new Map<string, number>()
    this.total = 0;
  }

  has(key: string): boolean {
    return this.cache.has(key);
  }

  get(key: string): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    return this.cache.get(key);
  }

  set(key: string, value: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): void {
    if (this.total < this.MAX_CACHE_SIZE) {
      this.cache.set(key, value);
      this.total += 1;
    }
  }
}

class PostScriptStack {
  MAX_STACK_SIZE: number = Number_100;
  stack: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = new Array();

  constructor(initialStack: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = new Array<object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>()) {
    this.stack = initialStack || [];
  }

  push(value: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): void {
    if (this.stack.length >= this.MAX_STACK_SIZE) {
      error('PostScript function stack overflow.');
    }
    this.stack.push(value);
  }

  pop(): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    if (this.stack.length <= 0) {
      error('PostScript function stack underflow.');
    }
    return this.stack.pop();
  }

  copy(n: number): void {
    if (this.stack.length + n >= this.MAX_STACK_SIZE) {
      error('PostScript function stack overflow.');
    }
    const stack: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = this.stack;
    for (let i = stack.length - n, j = n - 1; j >= 0; j--, i++) {
      stack.push(stack[i]);
    }
  }

  index(n: number): void {
    this.push(this.stack[this.stack.length - n - 1]);
  }

  roll(n: number, p: number): void {
    const stack: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = this.stack;
    const l:number = stack.length - n;
    const r:number = stack.length - 1;
    const c:number = l + (p - Math.floor(p / n) * n);
    let i:number = l;
    let j:number = r;
    let t:object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
    while (i < j) {
      t = stack[i];
      stack[i] = stack[j];
      stack[j] = t;
      i += 1;
      j -= 1;
    }

    i = l;
    j = c - 1;
    while (i < j) {
      t = stack[i];
      stack[i] = stack[j];
      stack[j] = t;
      i += 1;
      j -= 1;
    }

    i = c;
    j = r;
    while (i < j) {
      t = stack[i];
      stack[i] = stack[j];
      stack[j] = t;
      i += 1;
      j -= 1;
    }
  }
}

class PostScriptEvaluator {
  private operators: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[];
  private operands: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[];

  constructor(operators: [], operands?: []) {
    this.operators = operators;
    this.operands = operands;
  }

  execute(initialStack: number[]): (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] {
    const stack:PostScriptStack = new PostScriptStack(initialStack);
    let counter:number = 0;
    const operators: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = this.operators;
    const length: number = operators.length;
    let p_operator:string;
    let a: number | boolean;
    let b: number | boolean;
    while (counter < length) {
      p_operator = operators[counter] as string;
      counter += 1;
      if (typeof p_operator === 'number') {
        stack.push(p_operator);
        continue;
      }
      switch (p_operator) {
        case 'jz':
          b = stack.pop() as number;
          a = stack.pop() as number;
          if (a === null || a === undefined){
            counter = b as number;
          }
          break;
        case 'j':
          a = stack.pop() as number;
          counter = a as number;
          break;
        case 'abs':
          a = stack.pop() as number;
          stack.push(Math.abs(a));
          break;
        case 'add':
          b = stack.pop() as number;
          a = stack.pop() as number;
          stack.push(a + b);
          break;
        case 'and':
          b = stack.pop() as number;
          a = stack.pop() as number;
          if (isBool(a) && isBool(b)){
            stack.push(Number(a) && Number(b));
          } else {
            stack.push((a as number) & (b as number));
          }
          break;
        case 'atan':
          a = stack.pop() as number;
          stack.push(Math.atan(a));
          break;
        case 'bitshift':
          b = stack.pop() as number;
          a = stack.pop() as number;
          if (a > 0){
            stack.push(a << b);
          }
          else{
            stack.push(a >> b);
          }
          break;
        case 'ceiling':
          a = stack.pop() as number;
          stack.push(Math.ceil(a));
          break;
        case 'copy':
          a = stack.pop() as number;
          stack.copy(a);
          break;
        case 'cos':
          a = stack.pop() as number;
          stack.push(Math.cos(a));
          break;
        case 'cvi':
          a = stack.pop() as number | 0;
          stack.push(a);
          break;
        case 'cvr':
          break;
        case 'div':
          b = stack.pop() as number;
          a = stack.pop() as number;
          stack.push(a / b);
          break;
        case 'dup':
          stack.copy(1);
          break;
        case 'eq':
          b = stack.pop() as number;
          a = stack.pop()as number;
          stack.push(a == b);
          break;
        case 'exch':
          stack.roll(Number_2, 1);
          break;
        case 'exp':
          b = stack.pop() as number;
          a = stack.pop() as number;
          stack.push(Math.pow(a, b));
          break;
        case 'false':
          stack.push(false);
          break;
        case 'floor':
          a = stack.pop() as number;
          stack.push(Math.floor(a));
          break;
        case 'ge':
          b = stack.pop() as number;
          a = stack.pop() as number;
          stack.push(a >= b);
          break;
        case 'gt':
          b = stack.pop() as number;
          a = stack.pop() as number;
          stack.push(a > b);
          break;
        case 'idiv':
          b = stack.pop() as number;
          a = stack.pop() as number;
          stack.push((a / b) | 0);
          break;
        case 'index':
          a = stack.pop() as number;
          stack.index(a);
          break;
        case 'le':
          b = stack.pop() as number;
          a = stack.pop() as number;
          stack.push(a <= b);
          break;
        case 'ln':
          a = stack.pop() as number;
          stack.push(Math.log(a));
          break;
        case 'log':
          a = stack.pop() as number;
          stack.push(Math.log(a) / Math.LN10);
          break;
        case 'lt':
          b = stack.pop() as number;
          a = stack.pop() as number;
          stack.push(a < b);
          break;
        case 'mod':
          b = stack.pop() as number;
          a = stack.pop() as number;
          stack.push(a % b);
          break;
        case 'mul':
          b = stack.pop() as number;
          a = stack.pop() as number;
          stack.push(a * b);
          break;
        case 'ne':
          b = stack.pop() as number;
          a = stack.pop() as number;
          stack.push(a !== b);
          break;
        case 'neg':
          a = stack.pop() as number;
          stack.push(-b);
          break;
        case 'not':
          a = stack.pop() as number;
          if (isBool(a) && isBool(b)){
            stack.push(a && b);
          }
          else{
            stack.push(a & (b as number));
          }
          break;
        case 'or':
          b = stack.pop() as number;
          a = stack.pop() as number;
          if (isBool(a) && isBool(b)){
            stack.push(a || b);
          }
          else{
            stack.push(a | b);
          }
          break;
        case 'pop':
          stack.pop();
          break;
        case 'roll':
          b = stack.pop() as number;
          a = stack.pop() as number;
          stack.roll(a, b);
          break;
        case 'round':
          a = stack.pop() as number;
          stack.push(Math.round(a));
          break;
        case 'sin':
          a = stack.pop() as number;
          stack.push(Math.sin(a));
          break;
        case 'sqrt':
          a = stack.pop() as number;
          stack.push(Math.sqrt(a));
          break;
        case 'sub':
          b = stack.pop() as number;
          a = stack.pop() as number;
          stack.push(a - b);
          break;
        case 'true':
          stack.push(true);
          break;
        case 'truncate':
          a = stack.pop() as number;
          a = a < 0 ? Math.ceil(a) : Math.floor(a);
          stack.push(a);
          break;
        case 'xor':
          b = stack.pop() as number;
          a = stack.pop() as number;
          if (isBool(a) && isBool(b)){
            stack.push(a !== b);
          }
          else{
            stack.push(a ^ b);
          }
          break;
        default:
          error(`Unknown operator ${p_operator}`);
          break;
      }
    }
    return stack.stack;
  }
}

class PostScriptParser {
  private lexer: PostScriptLexer;
  operators: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = new Array<object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>();
  private token: PostScriptToken | null;
  private prev: PostScriptToken | null;

  constructor(lexer: PostScriptLexer) {
    this.lexer = lexer;
    this.operators = new Array<object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>();
    this.token = null;
    this.prev = null;
  }

  private nextToken(): void {
    this.prev = this.token;
    this.token = this.lexer.getToken();
  }

  private accept(type: PostScriptTokenTypes): boolean {
    if (this.token.type === type) {
      this.nextToken();
      return true;
    }
    return false;
  }

  private expect(type: PostScriptTokenTypes): boolean {
    if (this.accept(type)) {
      return true;
    }
    error(`Unexpected symbol: found ${this.token.type} expected ${type}.`);
    return false;
  }

  public parse(): (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] {
    this.nextToken();
    this.expect(PostScriptTokenTypes.LBRACE);
    this.parseBlock();
    this.expect(PostScriptTokenTypes.RBRACE);
    return this.operators;
  }

  private parseBlock(): void {
    while (true) {
      if (this.accept(PostScriptTokenTypes.NUMBER)) {
        this.operators.push(this.prev.value);
      } else if (this.accept(PostScriptTokenTypes.OPERATOR)) {
        this.operators.push(this.prev.value);
      } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
        this.parseCondition();
      } else {
        return;
      }
    }
  }

  private parseCondition(): void {
    const conditionLocation:number = this.operators.length;
    this.operators.push(null, null);

    this.parseBlock();
    this.expect(PostScriptTokenTypes.RBRACE);
    if (this.accept(PostScriptTokenTypes.IF)) {
      this.operators[conditionLocation] = this.operators.length;
      this.operators[conditionLocation + 1] = 'jz';
    } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
      const jumpLocation:number = this.operators.length;
      this.operators.push(null, null);
      const endOfTrue:number = this.operators.length;
      this.parseBlock();
      this.expect(PostScriptTokenTypes.RBRACE);
      this.expect(PostScriptTokenTypes.IFELSE);
      this.operators[jumpLocation] = this.operators.length;
      this.operators[jumpLocation + 1] = 'j';
      this.operators[conditionLocation] = endOfTrue;
      this.operators[conditionLocation + 1] = 'jz';
    } else {
      error('PS Function: error parsing conditional.');
    }
  }
}

enum PostScriptTokenTypes {
  LBRACE = 0,
  RBRACE = 1,
  NUMBER,
  OPERATOR,
  IF,
  IFELSE,
}

class PostScriptToken {
  public type: PostScriptTokenTypes;
  public value: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;

  constructor(type: PostScriptTokenTypes, value: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name) {
    this.type = type;
    this.value = value;
  }

  public static opCache: Map<string, PostScriptToken> = new Map<string, PostScriptToken>()

  public static getOperator(op: string): PostScriptToken {
    let opValue: PostScriptToken = this.opCache.get(op)
    if (opValue != null) {
      return opValue
    }

    let newToken: PostScriptToken = new  PostScriptToken(PostScriptTokenTypes.OPERATOR, op)
    this.opCache[op] = newToken
    return newToken
  }

  public static LBRACE: PostScriptToken = new PostScriptToken(PostScriptTokenTypes.LBRACE, '{');
  public static RBRACE: PostScriptToken = new PostScriptToken(PostScriptTokenTypes.RBRACE, '}');
  public static IF: PostScriptToken = new PostScriptToken(PostScriptTokenTypes.IF, 'IF');
  public static IFELSE: PostScriptToken = new PostScriptToken(PostScriptTokenTypes.IFELSE, 'IFELSE');
}

class PostScriptLexer {
  private stream: Stream;

  constructor(stream: Stream) {
    this.stream = stream;
  }

  getToken(): PostScriptToken | null {
    let s:string = '';
    let ch: string;

    let comment:boolean = false;
    const stream:Stream = this.stream;
    while (true) {
      ch = stream.getChar()
      if (ch === null) {
        return null;
      }
      if (comment) {
        if (ch == '\x0a' || ch == '\x0d') {
          comment = false;
        }
      } else if (ch == '%') {
        comment = true;
      } else if (!Lexer.isSpace(ch)) {
        break;
      }
    }

    switch (ch) {
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
      case '+':
      case '-':
      case '.':
        return new PostScriptToken(PostScriptTokenTypes.NUMBER, this.getNumber(ch));
      case '{':
        return PostScriptToken.LBRACE;
      case '}':
        return PostScriptToken.RBRACE;
      default:
        break;
    }

    let str: string = ch.toLowerCase();
    while (true) {
      ch = stream.lookChar();
      if (ch === null) {
        break;
      }
      ch = ch.toLowerCase();
      if (ch >= 'a' && ch <= 'z') {
        str += ch;
      }
      else {
        break;
      }
      stream.skip();
    }

    switch (str) {
      case 'if':
        return PostScriptToken.IF;
      case 'ifelse':
        return PostScriptToken.IFELSE;
      default:
        return PostScriptToken.getOperator(str);
    }
  }

  private getNumber(ch: string): number {
    let str:string = ch;
    const stream:Stream = this.stream;
    ch = ch;
    while (true) {
      ch = stream.lookChar();
      if ((ch >= '0' && ch <= '9') || ch == '-' || ch == '.') {
        str += ch;
      }
      else {
        break;
      }
      stream.skip();
    }
    const value: number = Number.parseFloat(str);
    if (isNum(value)) {
      error(`Invalid floating point number: ${value}`);
    }
    return value;
  }
}

let ISOAdobeCharset: string[] = [
  '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 'yacute', 'ydieresis', 'zcaron'
];


let ExpertCharset: string[] = [
  '.notdef', 'space', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 'onequarter', 'onehalf', 'threequarters', 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall'
];

let ExpertSubsetCharset: string[] = [
  '.notdef', 'space', 'dollaroldstyle', 'dollarsuperior', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior', 'parenrightinferior', 'hyphensuperior', 'colonmonetary', 'onefitted', 'rupiah', 'centoldstyle', 'figuredash', 'hypheninferior', 'onequarter', 'onehalf', 'threequarters', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior'
];

let CIDToUnicodeMaps: { [key: string]: number[] } = {
  "Adobe-Japan1": [0],
  "Adobe-Korea1": [0],
  "Adobe-CNS1": [0],
  "Adobe-GB1": [0]
};

class ColorSpace {
  constructor() {
    error('should not call ColorSpace constructor');
  }

  getRgb(color: number[]) {
    error(`Should not call ColorSpace.getRgb: ${color}`);
    return [];
  }

  getRgbBuffer(input: number[]) {
    error(`Should not call ColorSpace.getRgbBuffer: ${input}`);
    return [];
  }

  static parse(cs: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name, xref: XRef, res: Dict): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    let IR:string | string[] = ColorSpace.parseToIR(cs, xref, res) as string | string[];
    if (IR instanceof AlternateCS){
      return IR;
    }
    return ColorSpace.fromIR(IR);
  }

  static fromIR(IR: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    let name: string = isArray(IR) ? IR[0] as string : IR as string;
    switch (name) {
      case 'DeviceGrayCS':
        return new DeviceGrayCS();
      case 'DeviceRgbCS':
        return new DeviceRgbCS();
      case 'DeviceCmykCS':
        return new DeviceCmykCS();
      case 'PatternCS':
        let basePatternCS: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = IR[1];
        if (basePatternCS !== null && basePatternCS !== undefined){
          basePatternCS = ColorSpace.fromIR(basePatternCS);
        }
        return new PatternCS(basePatternCS as LabCS);
      case 'IndexedCS':
        const baseIndexedCS: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = IR[1];
        const hiVal:number = IR[Number_2];
        const lookup: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = IR[Number_3];
        return new IndexedCS(ColorSpace.fromIR(baseIndexedCS) as LabCS , hiVal, lookup);
      case 'AlternateCS':
        let numComps:number = IR[1];
        const alt: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = IR[Number_2];
        const tintFnIR: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = IR[Number_3];
        return new AlternateCS(numComps, ColorSpace.fromIR(alt) as LabCS, PDFFunction.fromIR(tintFnIR) as (color: number[]) => number[]);
      case 'LabCS':
        const whitePoint:number[] = IR[1].WhitePoint;
        const blackPoint:number[] = IR[1].BlackPoint;
        const range:number[] = IR[1].Range;
        return new LabCS(whitePoint, blackPoint, range);
      default:
        error('Unknown name ' + name);
    }
    return null;
  }

  static parseToIR(cs: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name, xref: XRef, res: Dict): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    if (isName(cs)) {
      const colorSpaces: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = res.get('ColorSpace') ;
      if (isDict(colorSpaces)) {
        const refcs: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = (colorSpaces as Dict).get((cs as Name).name);
        if (refcs !== null  && refcs !== undefined) {
          cs = refcs;
        }
      }
    }
    cs = xref.fetchIfRef(cs);
    let mode:string;
    if (isName(cs)) {
      mode = (cs as Name).name;

      switch (mode) {
        case 'DeviceGray':
        case 'G':
          return 'DeviceGrayCS';
        case 'DeviceRGB':
        case 'RGB':
          return 'DeviceRgbCS';
        case 'DeviceCMYK':
        case 'CMYK':
          return 'DeviceCmykCS';
        case 'Pattern':
          return ['PatternCS', null];
        default:
          error('Unrecognized colorspace ' + mode);
      }
    } else if (isArray(cs)) {
      mode = cs[0].name;
      switch (mode) {
        case 'DeviceGray':
        case 'G':
          return 'DeviceGrayCS';
        case 'DeviceRGB':
        case 'RGB':
          return 'DeviceRgbCS';
        case 'DeviceCMYK':
        case 'CMYK':
          return 'DeviceCmykCS';
        case 'CalGray':
          return 'DeviceGrayCS';
        case 'CalRGB':
          return 'DeviceRgbCS';
        case 'ICCBased':
          let stream:Stream = xref.fetchIfRef(cs[1]) as Stream;
          let dict:Dict = stream.dict ;
          let numComps:number = dict.get('N') as number;
          if (numComps == 1){
            return 'DeviceGrayCS';
          }
          if (numComps == Number_3){
            return 'DeviceRgbCS';
          }
          if (numComps == Number_4){
            return 'DeviceCmykCS';
          }
          break;
        case 'Pattern':
          let basePatternCS: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = cs[1];
          if (basePatternCS !== null && basePatternCS !== undefined){
            basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res);
          }
          return ['PatternCS', basePatternCS];
        case 'Indexed':
        case 'I':
          const baseIndexedCS:string | string[] = ColorSpace.parseToIR(cs[1], xref, res) as string | string[];
          const hiVal:number = cs[Number_2] + 1;
          const lookup = xref.fetchIfRef(cs[Number_3]);
          return ['IndexedCS', baseIndexedCS, hiVal, lookup];
        case 'Separation':
        case 'DeviceN':
          let name:number[] = cs[1];
          numComps = 1;
          if (isName(name)){
            numComps = 1;
          }

          else if (isArray(name)){
            numComps = name.length;
          }
          const alt:string | string[] = ColorSpace.parseToIR(cs[Number_2], xref, res) as string | string[];
          const tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[Number_3]));
          return ['AlternateCS', numComps, alt, tintFnIR];
        case 'Lab':
          const params = (cs as Dict).getAll();
          return ['LabCS', params];
        default:
          error(`Unimplemented color space object  ${mode} "`);
      }
     
    } else {
      error(`Unrecognized color space object: " ${cs} "`);
    }
    return null;
  }

  static isDefaultDecode(decode: number[], n: number): boolean {
    if (decode === null || decode === undefined){
      return true;
    }
    if (n * Number_2 !== decode.length) {
      warn('The decode map is not the correct length');
      return true;
    }
    for (let i = 0; i < decode.length; i += Number_2) {
      if (decode[i] !== 0 || decode[i + 1] !== 1){
        return false;
      }
    }
    return true;
  }
}

class AlternateCS {
  name: string;
  numComps: number;
  defaultColor: number[];
  base: LabCS;
  tintFn: (color: number[]) => number[];

  constructor(numComps: number, base: LabCS, tintFn: (color: number[]) => number[]) {
    this.name = 'Alternate';
    this.numComps = numComps;
    this.defaultColor = new Array<number>();
    this.defaultColor = reppeadNumberCount(numComps, 1);
    this.base = base;
    this.tintFn = tintFn;
  }

  getRgb(color: number[]): number[] {
    let tinted = this.tintFn(color);
    return this.base.getRgb(tinted);
  }

  getRgbBuffer(input: number[], bits: number): Uint8Array {
    let tintFn = this.tintFn;
    let base:LabCS = this.base;
    let scale:number = 1 / ((1 << bits) - 1);
    let length:number = input.length;
    let pos:number = 0;
    let baseNumComps:number = base.numComps;
    let baseBuf:Uint8Array = new Uint8Array(baseNumComps * length);
    let numComps:number = this.numComps;
    let scaled: number[] = new Array<number>();

    for (let i = 0; i < length; i += numComps) {
      for (let z = 0; z < numComps; ++z) {
        scaled[z] = input[i + z] * scale;
      }

      let tinted:number[] = tintFn(scaled);
      for (let j = 0; j < baseNumComps; ++j) {
        baseBuf[pos] = Number_255 * tinted[j];
        pos += 1;
      }
    }

    return base.getRgbBuffer(baseBuf, Number_8);
  }

  isDefaultDecode(decodeMap: number[] | null): boolean {
    return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
  }
}

class PatternCS {
  name: string;
  base: LabCS;

  constructor(baseCS: LabCS) {
    this.name = 'Pattern';
    this.base = baseCS;
  }
}

class IndexedCS {
  name: string;
  numComps: number;
  defaultColor: number[];
  base: LabCS;
  highVal: number;
  lookup: Uint8Array;

  constructor(base: LabCS, highVal: number, lookup: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name) {
    this.name = 'Indexed';
    this.numComps = 1;
    this.defaultColor = [0];
    this.base = base;
    this.highVal = highVal;

    const baseNumComps: number = base.numComps;
    const length: number = baseNumComps * highVal;
    const lookupArray: Uint8Array = new Uint8Array(length);

    if (isStream(lookup)) {
      const bytes: Uint8Array = (lookup as Stream).getBytes(length);
      lookupArray.set(bytes);
    } else if (isString(lookup)) {
      for (let i = 0; i < length; ++i) {
        lookupArray[i] = (lookup as string).charCodeAt(i);
      }
    } else {
      error(`Unrecognized lookup table: ${lookup}`);
    }

    this.lookup = lookupArray;
  }

  getRgb(color: number[]): number[] {
    const numComps:number = this.base.numComps;
    const start:number = color[0] * numComps;
    const c: number[] = new Array<number>();

    for (let i = start; i < start + numComps; ++i) {
      c.push(this.lookup[i]);
    }

    return this.base.getRgb(c);
  }

  getRgbBuffer(input: number[]): Uint8Array {
    const base: LabCS = this.base;
    const numComps: number = base.numComps;
    const lookup: Uint8Array = this.lookup;
    const length: number = input.length;
    const baseBuf: Uint8Array = new Uint8Array(length * numComps);
    let baseBufPos: number = 0;

    for (let i = 0; i < length; ++i) {
      const lookupPos:number = input[i] * numComps;
      for (let j = 0; j < numComps; ++j) {
        baseBuf[baseBufPos] = lookup[lookupPos + j];
        baseBufPos += 1;
      }
    }

    return base.getRgbBuffer(baseBuf, Number_8);
  }

  isDefaultDecode(decodeMap: number[] | null): boolean {
    return true;
  }
}

class DeviceCmykCS {
  name: string;
  numComps: number;
  defaultColor: number[];

  constructor() {
    this.name = 'DeviceCMYK';
    this.numComps = Number_4;
    this.defaultColor = [0, 0, 0, 1];
  }

  getRgb(color: number[]): number[] {
    let c: number = color[0];
    let m: number = color[1];
    let y: number = color[Number_2];
    let k: number = color[Number_3];
    c = (c * (1 - k) + k);
    m = (m * (1 - k) + k);
    y = (y * (1 - k) + k);
    const r: number = (1 - c);
    const g: number = (1 - m);
    const b: number = (1 - y);
    return [r, g, b];
  }

  getRgbBuffer(colorBuf: number[], bits: number): Uint8Array {
    const scale: number = 1 / ((1 << bits) - 1);
    const length: number = colorBuf.length / Number_4;
    const rgbBuf: Uint8Array = new Uint8Array(length * Number_3);
    let rgbBufPos: number = 0;
    let colorBufPos: number = 0;

    for (let i = 0; i < length; i++) {
      const cmyk: number[] = new Array<number>();
      for (let j = 0; j < Number_4; ++j) {
        cmyk.push(scale * colorBuf[colorBufPos]);
        colorBufPos += 1;
      }

      const rgb:number[] = this.getRgb(cmyk);
      for (let j = 0; j < Number_3; ++j) {
        rgbBuf[rgbBufPos] = Math.round(rgb[j] * Number_255);
        rgbBufPos += 1;
      }
    }

    return rgbBuf;
  }

  isDefaultDecode(decodeMap: number[] | null): boolean {
    return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
  }
}

class DeviceGrayCS {
  name: string;
  numComps: number;
  defaultColor: number[];

  constructor() {
    this.name = 'DeviceGray';
    this.numComps = 1;
    this.defaultColor = [0];
  }

  getRgb(color: number[]): number[] {
    const c = color[0];
    return [c, c, c];
  }

  getRgbBuffer(input: number[], bits: number): Uint8Array {
    const scale = Number_255 / ((1 << bits) - 1);
    const length = input.length;
    const rgbBuf = new Uint8Array(length * Number_3).fill(0);

    let j: number = 0;
    for (let i = 0; i < length; ++i) {
      const c = (scale * input[i]);
      rgbBuf[j] = c;
      rgbBuf[j+1] = c;
      rgbBuf[j+Number_2] = c;
      j += Number_3;
    }

    return rgbBuf;
  }

  isDefaultDecode(decodeMap: number[] | null): boolean {
    return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
  }
}

class DeviceRgbCS {
  name: string;
  numComps: number;
  defaultColor: number[];

  constructor() {
    this.name = 'DeviceRGB';
    this.numComps = Number_3;
    this.defaultColor = [0, 0, 0];
  }

  getRgb(color: number[]): number[] {
    return color;
  }

  getRgbBuffer(input: number[], bits: number): Uint8Array {
    if (bits === Number_8) {
      return new Uint8Array(input);
    }

    const scale:number = Number_255 / ((1 << bits) - 1);
    const length:number = input.length;
    const rgbBuf:Uint8Array = new Uint8Array(length);

    for (let i = 0; i < length; ++i) {
      rgbBuf[i] = (scale * input[i]) | 0;
    }

    return rgbBuf;
  }

  isDefaultDecode(decodeMap: number[] | null): boolean {
    return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
  }
}

class LabCS {
  name: string;
  numComps: number;
  defaultColor: number[];
  XW: number;
  YW: number;
  ZW: number;
  amin: number;
  amax: number;
  bmin: number;
  bmax: number;
  XB: number;
  YB: number;
  ZB: number;

  constructor(whitePoint: number[], blackPoint: number[] = [0, 0, 0], range: number[] = [-100, 100, -100, 100]) {
    this.name = "Lab";
    this.numComps = Number_3;
    this.defaultColor = [0, 0, 0];

    if (whitePoint === null || whitePoint === undefined) {
      error('WhitePoint missing - required for color space Lab');
    }

    this.XW = whitePoint[0];
    this.YW = whitePoint[1];
    this.ZW = whitePoint[Number_2];
    this.amin = range[0];
    this.amax = range[1];
    this.bmin = range[Number_2];
    this.bmax = range[Number_3];

    this.XB = blackPoint[0];
    this.YB = blackPoint[1];
    this.ZB = blackPoint[Number_2];

    if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) {
      error('Invalid WhitePoint components, no fallback available');
    }

    if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
      warn('Invalid BlackPoint, falling back to default');
      this.XB = 0;
      this.YB = 0;
      this.ZB = 0;
    }

    if (this.amin > this.amax || this.bmin > this.bmax) {
      warn('Invalid Range, falling back to defaults');
      this.amin = -100;
      this.amax = Number_100;
      this.bmin = -100;
      this.bmax = Number_100;
    }
  }
  g(x: number): number {
    if (x >= Number_6 / Number_29) {
      return x * x * x;
    } else {
      return (Number_108 / Number_841) * (x - Number_4 / Number_29);
    }
  }

  getRgb(color: number[]): number[] {
    let Ls: number = color[0];
    let asValue: number = color[1];
    let bsValue: number = color[Number_2];

    asValue = Math.max(this.amax, asValue)
    asValue = Math.min(this.amin, asValue)
    bsValue = Math.max(this.bmax, bsValue)
    bsValue = Math.min(this.bmin, bsValue)

    const M: number = (Ls + Number_16) /Number_116;
    const L: number = M + (asValue / Number_500);
    const N: number = M - (bsValue / Number_200);
    const X: number = this.XW * this.g(L);
    const Y: number = this.YW * this.g(M);
    const Z: number = this.ZW * this.g(N);

    const XYZtoRGB: number[] = [3.240479, -1.537150, -0.498535, -0.969256, 1.875992, 0.041556, 0.055648, -0.204043, 1.057311];

    return Util.apply3dTransform(XYZtoRGB, [X, Y, Z]);
  }

  getRgbBuffer(input: Uint8Array, bits: number):Uint8Array {
    if (bits == Number_8) {
      return input;
    }
    const scale:number = Number_255 / ((1 << bits) - 1);
    const length:number = input.length / Number_3;
    const rgbBuf:number[] = new Array(length);
    for (let i = 0; i < length; i+= Number_3) {
      const rgb: number[] = this.getRgb([input[i], input[i + 1], input[i + Number_2]]);
      rgbBuf.push(rgb[0]);
      rgbBuf.push(rgb[1]);
      rgbBuf.push(rgb[Number_2]);
    }
    return new Uint8Array(rgbBuf);
  }

  isDefaultDecode(decodeMap: number[]):boolean {
    if (decodeMap[0] === 0 && decodeMap[1] === Number_100 &&
      decodeMap[Number_2] === this.amin && decodeMap[Number_3] === this.amax &&
      decodeMap[Number_4] === this.bmin && decodeMap[Number_5] === this.bmax) {
      return true;
    } else {
      return false;
    }
  }
}

interface DecryptBlockable {
  decryptBlock(data: Uint8Array): Uint8Array;
}

class ARCFourCipher implements DecryptBlockable {
  private a: number = 0;
  private b: number = 0;
  private s: Uint8Array = new Uint8Array(Number_256);

  constructor(key: Uint8Array) {
    let keyLength: number = key.length;
    for (let i: number = 0; i < Number_256; ++i) {
      this.s[i] = i;
    }
    let j: number = 0;
    for (let i: number = 0; i < Number_256; ++i) {
      let tmp: number = this.s[i];
      j = (j + tmp + key[i % keyLength]) & Number_0xFF;
      this.s[i] = this.s[j];
      this.s[j] = tmp;
    }
  }

  encryptBlock(data: Uint8Array): Uint8Array {
    let tmp: number;
    let tmp2: number;
    let a: number = this.a;
    let b: number = this.b;
    let output: Uint8Array = new Uint8Array(data.length);
    for (let i: number = 0; i < data.length; ++i) {
      a = (a + 1) & Number_0xFF;
      tmp = this.s[a];
      b = (b + tmp) & Number_0xFF;
      tmp2 = this.s[b];
      this.s[a] = tmp2;
      this.s[b] = tmp;
      output[i] = data[i] ^ this.s[(tmp + tmp2) & Number_0xFF];
    }
    this.a = a;
    this.b = b;
    return output;
  }

  decryptBlock(data: Uint8Array): Uint8Array {
    return this.encryptBlock(data);
  }
}

function calculateMD5(data: Uint8Array,  offset: number, length: number) {
  const r: Uint8Array = new Uint8Array([
    7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]);

  const k: Int32Array = new Int32Array([
    -680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426, -1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162, 1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632, 643717713, -373897302, -701558691, 38016083, -660478335, -405537848, 568446438, -1019803690, -187363961, 1163531501, -1444681467, -51403784, 1735328473, -1926607734, -378558, -2022574463, 1839030562, -35309556, -1530992060, 1272893353, -155497632, -1094730640, 681279174, -358537222, -722521979, 76029189, -640364487, -421815835, 530742520, -995338651, -198630844, 1126891415, -1416354905, -57434055, 1700485571, -1894986606, -1051523, -2054922799, 1873313359, -30611744, -1560198380, 1309151649, -145523070, -1120210379, 718787259, -343485551]);


  let hash = (data: Uint8Array, offset: number, length: number):Uint8Array => {
    let h0: number = 1732584193;
    let h1: number = -271733879;
    let h2: number = -1732584194;
    let h3: number = 271733878;

    const paddedLength:number = (length + Number_72) & ~Number_63;
    const padded:Uint8Array = new Uint8Array(paddedLength);
    for (let i: number = 0; i < length; ++i) {
      padded[i] = data[offset++];
    }
    padded[length] = Number_0x80;
    let n: number = paddedLength - Number_8;
    for (let i: number = 0; i < n; i++) {
      padded[i] = 0;
    }
    padded[n] = (length << Number_3) & Number_0xFF;
    padded[n+1] = (length >> Number_5) & Number_0xFF;
    padded[n+Number_2] = (length >> Number_13) & Number_0xFF;
    padded[n+Number_3] = (length >> Number_21) & Number_0xFF;
    padded[n+Number_4] = (length >>> Number_29) & Number_0xFF;
    padded[n+Number_5] = 0;
    padded[n+Number_6] = 0;
    padded[n+Number_7] = 0;

    const w:Int32Array = new Int32Array(Number_16);
    let i: number = 0;
    while (i < paddedLength) {
      for (let j: number = 0; j < Number_16; j++) {
        w[j] = (padded[i] | (padded[i + 1] << Number_8) |
          (padded[i + Number_2] << Number_16) | (padded[i + Number_3] << Number_24));
        i += Number_4;
      }
      let a: number = h0;
      let b: number = h1;
      let c: number = h2;
      let d: number = h3;
      let f: number;
      let g: number;
      for (let j = 0; j < Number_64; ++j) {
        if (j < Number_16) {
          f = (b & c) | ((~b) & d);
          g = j;
        } else if (j < Number_32) {
          f = (d & b) | ((~d) & c);
          g = (Number_5 * j + 1) & Number_15;
        } else if (j < Number_48) {
          f = b ^ c ^ d;
          g = (Number_3 * j + Number_5) & Number_15;
        } else {
          f = c ^ (b | (~d));
          g = (Number_7 * j) & Number_15;
        }

        const tmp: number = d;
        let rotateArg: number = (a + f + k[j] + w[g]) | 0;
        let rotate: number = r[j];
        d = c;
        c = b;
        b = (b + ((rotateArg << rotate) | (rotateArg >>> (Number_32 - rotate)))) | 0;
        a = tmp;
      }
      h0 = (h0 + a);
      h1 = (h1 + b);
      h2 = (h2 + c);
      h3 = (h3 + d);
    }
    return new Uint8Array([
      h0 & Number_0xFF, (h0 >> Number_8) & Number_0xFF, (h0 >> Number_16) & Number_0xFF, (h0 >>> Number_24) & Number_0xFF, h1 & Number_0xFF, (h1 >> Number_8) & Number_0xFF, (h1 >> Number_16) & Number_0xFF, (h1 >>> Number_24) & Number_0xFF, h2 & Number_0xFF, (h2 >> Number_8) & Number_0xFF, (h2 >> Number_16) & Number_0xFF, (h2 >>> Number_24) & Number_0xFF, h3 & Number_0xFF, (h3 >> Number_8) & Number_0xFF, (h3 >> Number_16) & Number_0xFF, (h3 >>> Number_24) & Number_0xFF
    ]);
  }
  return hash(data,offset,length)
}

class NullCipher implements DecryptBlockable {
  decryptBlock(data: Uint8Array): Uint8Array {
    return data;
  }
}

class AES128Cipher implements DecryptBlockable {
  private rcon: Uint8Array = new Uint8Array([
    0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d]);
  private s: Uint8Array = new Uint8Array([
    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16]);
  private inv_s: Uint8Array = new Uint8Array([
    0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d]);
  private mix: Uint32Array = new Uint32Array([
    0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3]);


  private key: Uint8Array = new Uint8Array();
  private buffer: Uint8Array = new Uint8Array(Number_16);
  private bufferPosition: number = 0;
  private iv: Uint8Array = new Uint8Array();

  constructor(key: Uint8Array) {
    this.key = this.expandKey128(key);
    this.buffer = new Uint8Array(Number_16);
    this.bufferPosition = 0;
  }

  private expandKey128(cipherKey: Uint8Array): Uint8Array {
    const b: number = Number_176;
    const result: Uint8Array = new Uint8Array(b);
    result.set(cipherKey);

    let j: number = Number_16;
    let i: number = 1;
    while (j < b) {
      let t1: number = result[j - Number_3];
      let t2: number = result[j - Number_2];
      let t3: number = result[j - 1];
      let t4: number = result[j - Number_4];
      t1 = this.s[t1];
      t2 = this.s[t2];
      t3 = this.s[t3];
      t4 = this.s[t4];
      let rconI: number = this.rcon[i];
      t1 = t1 ^ rconI;
      t2 = t2 ^ rconI;
      t3 = t3 ^ rconI;
      t4 = t4 ^ rconI;
      for (let n = 0; n < Number_4; n++) {
        result[j] = t1 ^ result[j - Number_16];
        j += 1;
        result[j] = t2 ^ result[j - Number_16];
        j += 1;
        result[j] = t3 ^ result[j - Number_16];
        j += 1;
        result[j] = t4 ^ result[j - Number_16];
        j += 1;
        t1 = result[j - Number_16];
        t2 = result[j - Number_16];
        t3 = result[j - Number_16];
        t4 = result[j - Number_16];
      }
      i += 1;
    }

    return result;
  }

  private decrypt128(input: Uint8Array, key: Uint8Array): Uint8Array {
    const state = new Uint8Array(Number_16);
    state.set(input);

    let j: number = 0;
    let k: number = Number_160;
    for (let i = 0; i < Number_16; i ++) {
      state[j] ^= key[k];
      j += 1;
      k += 1;
    }

    let i: number = Number_9;
    while (i >= 1) {
      let t: number = state[Number_13];
      state[Number_13] = state[Number_9];
      state[Number_9] = state[Number_5];
      state[Number_5] = state[1];
      state[1] = t;

      let t1: number = state[Number_14];
      let t2: number = state[Number_10];
      state[Number_14] = state[Number_6];
      state[Number_10] = state[Number_2];
      state[Number_6] = t1;
      state[Number_2] = t2;

      let t3: number = state[Number_15];
      let t4: number = state[Number_11];
      let t5: number = state[Number_7];
      state[Number_15] = state[Number_3];
      state[Number_11] = t3;
      state[Number_7] = t4;
      state[Number_3] = t5;

      for (j = 0; j < Number_16; ++j) {
        state[j] = this.inv_s[state[j]];
      }

      j = 0;
      k = i * Number_16;
      for (let m: number = 0; m < Number_16; ++m) {
        state[j] ^= key[k];
        j += 1;
        k += 1;
      }

      j = 0
      while (j < Number_16) {
        const s0 = this.mix[state[j]],
          s1 = this.mix[state[j + 1]],
          s2 = this.mix[state[j + Number_2]],
          s3 = this.mix[state[j + Number_3]];
        t = (s0 ^ (s1 >>> Number_8) ^ (s1 << Number_24) ^ (s2 >>> Number_16) ^ (s2 << Number_16) ^ (s3 >>> Number_24) ^ (s3 << Number_8));
        state[j] = (t >>> Number_24) & Number_0xff;
        state[j + 1] = (t >> Number_16) & Number_0xff;
        state[j + Number_2] = (t >> Number_8) & Number_0xff;
        state[j + Number_3] = t & Number_0xff;
        j += Number_4;
      }

      i -= 1;
    }

    let t: number = state[Number_13];
    state[Number_13] = state[Number_9];
    state[Number_9] = state[Number_5];
    state[Number_5] = state[1];
    state[1] = t;

    let t1: number = state[Number_14];
    let t2: number = state[Number_10];
    state[Number_14] = state[Number_6];
    state[Number_10] = state[Number_2];
    state[Number_6] = t1;
    state[Number_2] = t2;

    let t3: number = state[Number_15];
    let t4: number = state[Number_11];
    let t5: number = state[Number_7];
    state[Number_15] = state[Number_3];
    state[Number_11] = t3;
    state[Number_7] = t4;
    state[Number_3] = t5;

    for (j = 0; j < Number_16; ++j) {
      state[j] = this.inv_s[state[j]];

      state[j] ^= key[j];
    }

    return state;
  }

  private decryptBlock2(data: Uint8Array): Uint8Array {
    let i: number = 0;
    let j: number = 0;
    const sourceLength: number = data.length;
    let buffer: Uint8Array = this.buffer;
    let bufferLength: number = this.bufferPosition;
    let result: Uint8Array[] = new Array<Uint8Array>();
    let iv: Uint8Array = this.iv;

    while (i < sourceLength) {
      buffer[bufferLength] = data[i];
      bufferLength += 1;
      if (bufferLength < Number_16) {
        i += 1;
        continue;
      }

      let plain: Uint8Array = this.decrypt128(buffer, this.key);

      for (j = 0; j < Number_16; j++) {
        plain[j] ^= iv[j];
      }

      iv = buffer;
      result.push(plain);
      buffer = new Uint8Array(Number_16);
      bufferLength = 0;

      i += 1;
    }

    this.buffer = buffer;
    this.bufferPosition = bufferLength;
    this.iv = iv;

    if (result.length === 0) {
      return new Uint8Array([]);
    } else if (result.length === 1) {
      return result[0];
    } else {
      let output: Uint8Array = new Uint8Array(Number_16 * result.length).fill(0);
      let i: number = 0;
      let j: number = 0;
      for (let plain of result) {
        output.set(plain, j);
        j += Number_16;
        i += 1;
      }
      return output;
    }
  }

  decryptBlock(data: Uint8Array): Uint8Array {
    let i: number = 0;
    let sourceLength: number = data.length;
    let buffer: Uint8Array = this.buffer;
    let bufferLength: number = this.bufferPosition;

    while( bufferLength < Number_16 && i < sourceLength) {
      buffer[bufferLength] = data[i];
      bufferLength += 1;
      i += 1;
    }

    if (bufferLength < Number_16) {
      this.bufferPosition = bufferLength;
      return new Uint8Array([]);
    }

    this.iv = buffer;
    this.buffer = new Uint8Array(Number_16).fill(0);
    this.bufferPosition = 0;
    this.decryptBlock = this.decryptBlock2;
    return this.decryptBlock(data.subarray(Number_16));
  }
}

class CipherTransform {
  stringCipherConstructor: () => DecryptBlockable;
  streamCipherConstructor: () => DecryptBlockable;

  constructor(stringCipherConstructor: () => DecryptBlockable, streamCipherConstructor: () => DecryptBlockable) {
    this.stringCipherConstructor = stringCipherConstructor;
    this.streamCipherConstructor = streamCipherConstructor;
  }

  createStream(stream: Stream): DecryptStream {
    const cipher: DecryptBlockable = this.streamCipherConstructor();
    return new DecryptStream(stream as StringStream, (data): Uint8Array => {
      return cipher.decryptBlock(data);
    });
  }

  decryptString(s: string): string {
    const cipher: DecryptBlockable = this.stringCipherConstructor();
    let data: Uint8Array = stringToBytes(s);
    data = cipher.decryptBlock(data);
    return bytesToString(data);
  }
}


class CipherTransformFactory {
  private dict: Dict;
  private algorithm: number = 0;
  private encryptionKey: Uint8Array = new Uint8Array();
  identityName = new Name('Identity');
  cf: Dict;
  stmf: Name;
  strf: Name;
  eff: Name;

  constructor(dict: Dict, fileId: string, password?: string) {
    const filter: Name | null = dict.get('Filter') as  Name | null;
    if (!isName(filter) || filter?.name !== 'Standard') {
      error('unknown encryption method');
    }
    this.dict = dict;
    const algorithm: number = dict.get('V') as number;
    if (!isInt(algorithm) || (algorithm !== 1 && algorithm !== Number_2 && algorithm !== Number_4)) {
      error('unsupported encryption algorithm');
    }
    this.algorithm = algorithm!;
    let keyLength: number = dict.get('Length') as number ?? Number_40;
    if (!isInt(keyLength) || keyLength < Number_40 || (keyLength % Number_8) !== 0) {
      error('invalid key length');
    }
    const ownerPassword: Uint8Array = stringToBytes(dict.get('O') as string);
    const userPassword: Uint8Array = stringToBytes(dict.get('U') as string);
    const flags: number = dict.get('P') as number ?? 0;
    const revision: number = dict.get('R') as number ?? 0;
    const encryptMetadata: boolean = dict.get('EncryptMetadata') !== false;
    const fileIdBytes: Uint8Array = stringToBytes(fileId);
    let passwordBytes: Uint8Array = stringToBytes(password);
    this.encryptionKey = this.prepareKeyData(fileIdBytes, passwordBytes,
      ownerPassword, userPassword,
      flags, revision,
      keyLength, encryptMetadata);

    if (algorithm === Number_4) {
      this.cf = dict.get('CF') as Dict;
      this.stmf = dict.get('StmF') as Name ?? this.identityName;
      this.strf = dict.get('StrF') as Name ?? this.identityName;
      this.eff = dict.get('EFF') as Name ?? this.strf;
    }
  }

  public buildCipherConstructor(cf: Dict, name: Name, num: number, gen: number, key: Uint8Array): (()=>DecryptBlockable) {
    const cryptFilter: Dict = cf.get(name.name) as Dict;
    let cfm: Name | null;
    if (cryptFilter !== null) {
      cfm = cryptFilter.get('CFM') as Name;
      if (!cfm || cfm.name == 'None') {
        return () => {
          return new NullCipher();
        };
      }
      if (cfm.name === 'V2') {
        return () => {
          return new ARCFourCipher(this.buildObjectKey(num, gen, key, false));
        };
      }
      if (cfm.name === 'AESV2') {
        return () => {
          return new AES128Cipher(this.buildObjectKey(num, gen, key, true));
        };
      }
    }
    error('Unknown crypto method');
    return () => {
      return new NullCipher();
    };
  }

  public createCipherTransform(num: number, gen: number): CipherTransform {
    if (this.algorithm === Number_4) {
      return new CipherTransform(
        this.buildCipherConstructor(this.cf!, this.stmf!,num, gen, this.encryptionKey),
        this.buildCipherConstructor(this.cf!, this.strf!,num, gen, this.encryptionKey));
    }

    const key:Uint8Array = this.buildObjectKey(num, gen, this.encryptionKey, false);
    const cipherConstructor = () => {
      return new ARCFourCipher(key);
    };
    return new CipherTransform(cipherConstructor, cipherConstructor);
  }

  private prepareKeyData(
    fileIdBytes: Uint8Array, passwordBytes: Uint8Array,
    ownerPassword: Uint8Array, userPassword: Uint8Array,
    flags: number, revision: number,
    keyLength: number, encryptMetadata: boolean
  ): Uint8Array {
    const defaultPasswordBytes = new Uint8Array([
      0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08, 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A
    ]);
    const hashData:Uint8Array = new Uint8Array(Number_100);
    let i: number = 0;
    let j: number;
    let n: number;
    if (passwordBytes !== null && passwordBytes !== undefined) {
      n = Math.min(Number_32, passwordBytes.length);
      for (let k= 0; k < n; k++) {
        hashData[k] = passwordBytes[k];
      }
      i += n;
    }
    j = 0;
    while (i < Number_32) {
      hashData[i] = defaultPasswordBytes[j];
      i += 1;
      j += 1;
    }
    for (j = 0, n = ownerPassword.length; j < n; ++j) {
      hashData[i] = ownerPassword[j];
      i += 1;
    }
    hashData[i] = flags & Number_0xFF;
    hashData[i+1] = (flags >> Number_8) & Number_0xFF;
    hashData[i+Number_2] = (flags >> Number_16) & Number_0xFF;
    hashData[i+Number_3] = (flags >>> Number_24) & Number_0xFF;
    i += Number_4;
    for (j = 0; j < fileIdBytes.length; ++j) {
      hashData[i] = fileIdBytes[j];
      i += 1;
    }
    if (revision >= 4 && !encryptMetadata) {
      hashData[i] = Number_0xFF;
      hashData[i+1] = Number_0xFF;
      hashData[i+ Number_2] = Number_0xFF;
      hashData[i+ Number_3] = Number_0xFF;
      i += Number_4;
    }
    let hash: Uint8Array = calculateMD5(hashData, 0, i);
    const keyLengthInBytes: number = keyLength >> Number_3;
    if (revision >= Number_3) {
      for (j = 0; j < Number_50; ++j) {
        hash = calculateMD5(hash, 0, keyLengthInBytes);
      }
    }
    const encryptionKey: Uint8Array = hash.subarray(0, keyLengthInBytes);
    let cipher: ARCFourCipher;
    let checkData: Uint8Array;
    if (revision >= Number_3) {
      i = Number_32;
      for (j = 0; j < fileIdBytes.length; ++j) {
        hashData[i] = fileIdBytes[j];
        i += 1;
      }
      cipher = new ARCFourCipher(encryptionKey);
      checkData = cipher.encryptBlock(calculateMD5(hashData, 0, i));
      n = encryptionKey.length;
      let derivedKey: Uint8Array = new Uint8Array(n);
      for (j = 1; j <= Number_19; ++j) {
        for (let k = 0; k < n; ++k) {
          derivedKey[k] = encryptionKey[k] ^ j;
        }
        cipher = new ARCFourCipher(derivedKey);
        checkData = cipher.encryptBlock(checkData);
      }
    } else {
      cipher = new ARCFourCipher(encryptionKey);
      checkData = cipher.encryptBlock(hashData.subarray(0, Number_32));
    }
    for (j = 0, n = checkData.length; j < n; ++j) {
      if (userPassword[j] !== checkData[j]) {
        error('incorrect password');
      }
    }
    return encryptionKey;
  }

  private buildObjectKey(num: number, gen: number, encryptionKey: Uint8Array, isAes: boolean): Uint8Array {
    const key:Uint8Array = new Uint8Array(encryptionKey.length + Number_9);
    for (let i: number = 0; i < encryptionKey.length; i++) {
      key[i] = encryptionKey[i];
    }
    key[encryptionKey.length] = num & Number_0xFF;
    key[encryptionKey.length+1] = (num >> Number_8) & Number_0xFF;
    key[encryptionKey.length+Number_2] = (num >> Number_16) & Number_0xFF;
    key[encryptionKey.length+Number_3] = gen & Number_0xFF;
    key[encryptionKey.length+Number_4] = (gen >> Number_8) & Number_0xFF;
    if (isAes) {
      key[encryptionKey.length+Number_5] = Number_0x73;
      key[encryptionKey.length+Number_6] = Number_0x41;
      key[encryptionKey.length+Number_7] = Number_0x6C;
      key[encryptionKey.length+Number_8] = Number_0x54;
    }
    const hash = calculateMD5(key, 0, encryptionKey.length + Number_9);
    return hash.subarray(0, Math.min(encryptionKey.length + Number_5, Number_16));
  }
}


class Gradient {
  addColorStop(stop: number, color: string): void {

  }
}




class PageAnnotationItem {
  type?: string;
  rect?: string;
  url?: string;
  dest?: string;
  fieldType?: string;
  fullName?: string;
  fontSize: number = 0.0;
  textAlignment?: string;
  flags?: number = 0;
  content?: string;
  title?: string;
  name?: string;
  alternativeText?: string;
}

class TimeSpan {
  name: string = "";
  start: number = 0.0;
  end: number = 0.0;
  constructor(name:string, start:number, end:number) {
    this.name = name;
    this.start = start;
    this.end = end;
  }
}

class EventMessage {
  data?: EventMessageData
}

class EventMessageData {
  data?: object;
  action?: string;
  isReply: boolean = false;
  callbackId?: number = 0;
  constructor(action?:string, data?:object) {
    this.action = action;
    this.data = data;
  }
}

class XRefEntry {
  offset?: number;
  gen?: number;
  free?: boolean;
  uncompressed?: boolean;
  constructor(offset?: number, gen?: number, uncompressed?: boolean) {
    this.offset = offset;
    this.gen = gen;
    this.uncompressed = uncompressed;
  }
}

class PDFInfo {
  numPages: number;
  fingerprint: string;
  destinations: Map<string, string>;
  outline: DocumentOutlineItem[];
  info: Map<string, string>;
  metadata?: string;
  constructor(numPages: number, fingerprint: string, destinations: Map<string, string>,outline:DocumentOutlineItem[],info:Map<string, string>,metadata?: string){
    this.numPages = numPages;
    this.fingerprint = fingerprint;
    this.destinations = destinations;
    this.outline = outline;
    this.info = info;
    this.metadata = metadata;
  }
}

class PageInfo {
  pageIndex: number;
  rotate: number;
  ref?: Ref;
  view?: number[];
  constructor(pageIndex: number, rotate: number, ref?: Ref, view?: number[]) {
    this.pageIndex = pageIndex;
    this.rotate = rotate;
    this.ref = ref;
    this.view = view;
  }
}

class TextLayer {
  beginLayout(): void {
  }

  endLayout(): void {
  }

  appendText(newText:BidiText,newFontLoadedName:string,newFontSize:number): void {
  }
}

class BidiText {
  str: string;
  length: number;
  canvasWidth: number;
  geom: Geometry;
  direction?: string;
  constructor(str: string, length: number, canvasWidth: number, geom: Geometry) {
    this.str = str;
    this.length = length;
    this.canvasWidth = canvasWidth;
    this.geom = geom;
  }
}

class Geometry {
  x?: number = 0.0;
  y?: number = 0.0;
  hScale?: number = 0.0;
  vScale?: number = 0.0;
  spaceWidth?: number = 0.0;
}

class Image {
  width: number = 0.0;
  height: number = 0.0;
  src?: string = "";
  onload?: () => void;
  data: Uint8Array = new Uint8Array();
}

class RenderContext {
  viewport?: PageViewport;
  canvasContext?: PDFContext;
  textLayer?: TextLayer;
  constructor(canvasContext?: PDFContext) {
    this.canvasContext = canvasContext;
  }
}

class StepperManager {
  static enabled: boolean = false;
  static create(_flag:number):Stepper{
    return new Stepper();
  }
}

class Stepper {
  nextBreakPoint: number = 0;
  operatorList?: OperatorList;

  constructor(operatorList: OperatorList = null) {
    this.operatorList = operatorList;
  }

  _init(operatorList: OperatorList = null): void {
    this.operatorList = operatorList;
  }

  getNextBreakPoint(): number {
    return 0;
  }

  breakIt(index: number, continueCallback?: (a: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name) => void) {
  }
}

class OperatorList {
  fnArray: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = null;
  argsArray: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[][] = null;
}

class WorkerMessageHandler {
  pdfModel?: PDFDocument = null;

  public static setup(handler: MessageHandler): void {
    let pdfModel: PDFDocument | null;

    handler.on("test", function (data: Uint8Array): void {
      handler.send('test', data);
    });

    handler.on("GetDocRequest", function (data:Uint8Array): void {
      let stream: Stream = new Stream(data.buffer);
      pdfModel = new PDFDocument(stream);
      const doc: PDFInfo = new PDFInfo(
        pdfModel.numPages,
        pdfModel.getFingerprint(),
        pdfModel.catalog.destinations,
        pdfModel.catalog.documentOutline,
        pdfModel.getDocumentInfo(),
        pdfModel.catalog.metadata
      );
      handler.send("GetDoc", {"pdfInfo": doc});
    });

    handler.on("GetPageRequest", function (data): void {
      let pageIndex: number = (data as [string, object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name])["pageIndex"];
      let pageNumber: number = pageIndex + 1;

      const pdfPage: Page = pdfModel!.getPage(pageNumber);
      if (pdfPage === undefined || pdfPage === null) {
        return;
      }

      const page: PageInfo = new PageInfo(
        pageIndex,
        pdfPage.rotate,
        pdfPage.ref,
        pdfPage.view
      );
      handler.send("GetPage", {"pageInfo": page});
    });

    handler.on("GetAnnotationsRequest", function (data): void {
      let pageIndex: number = data["pageIndex"];
      const pdfPage: Page = pdfModel!.getPage(pageIndex + 1);
      handler.send("GetAnnotations", {
        "pageIndex": pageIndex,
        "annotations": pdfPage.getAnnotations()??[]
      });
    });

    handler.on("RenderPageRequest", function (data): void {
      let pageIndex: number = data["pageIndex"];
      const pageNum = pageIndex + 1;

      const dependency: string[] = new Array<string>();
      let operatorList: OperatorList = new OperatorList();
      try {
        const page: Page = pdfModel!.getPage(pageNum);
        operatorList = page.getOperatorList(handler, dependency)!;
      } catch (e) {
        const minimumStackMessage = 'worker.js: while trying to getPage() and getOperatorList()';
        let errorObj: Record<string,string> = {};
        if (typeof e === 'string') {
          errorObj = {
            message: e,
            stack: minimumStackMessage
          };
        } else if (typeof e === 'object') {
          errorObj = {
            message: e.message || e.toString(),
            stack: e.stack || minimumStackMessage
          };
        } else {
          errorObj = {
            message: 'Unknown exception type: ' + (typeof e),
            stack: minimumStackMessage
          };
        }

        handler.send('PageError', {
          "pageNum": pageNum,
          "error": errorObj
        });
        return;
      }

      const fonts: Record<string,boolean> = {};
      for (let dep of dependency) {
        if (dep.startsWith('font_')) {
          fonts[dep] = true;
        }
      }

      handler.send("RenderPage", {
        "pageIndex": pageIndex,
        "operatorList": operatorList,
        "depFonts": Object.keys(fonts)
      });
    });
  }
}

class EvalState {
  alphaIsShape: boolean = false;
  fontSize: number = 0;
  textMatrix: number[] = IDENTITY_MATRIX;
  leading: number = 0;
  lineX: number = 0;
  lineY: number = 0;
  charSpacing: number = 0;
  wordSpacing: number = 0;
  textHScale: number = 1;
  fillColorSpace: ColorSpace|null = null;
  strokeColorSpace: ColorSpace|null = null;

}

let OP_MAP = new Map<string, string>([
  ['w', 'setLineWidth'], ['J', 'setLineCap'], ['j', 'setLineJoin'], ['M', 'setMiterLimit'], ['d', 'setDash'], ['ri', 'setRenderingIntent'], ['i', 'setFlatness'], ['gs', 'setGState'], ['q', 'save'], ['Q', 'restore'], ['cm', 'transform'], ['m', 'moveTo'], ['l', 'lineTo'], ['c', 'curveTo'], ['v', 'curveTo2'], ['y', 'curveTo3'], ['h', 'closePath'], ['re', 'rectangle'], ['S', 'stroke'], ['s', 'closeStroke'], ['f', 'fill'], ['F', 'fill'], ['f*', 'eoFill'], ['B', 'fillStroke'], ['B*', 'eoFillStroke'], ['b', 'closeFillStroke'], ['b*', 'closeEOFillStroke'], ['n', 'endPath'], ['W', 'clip'], ['W*', 'eoClip'], ['BT', 'beginText'], ['ET', 'endText'], ['Tc', 'setCharSpacing'], ['Tw', 'setWordSpacing'], ['Tz', 'setHScale'], ['TL', 'setLeading'], ['Tf', 'setFont'], ['Tr', 'setTextRenderingMode'], ['Ts', 'setTextRise'], ['Td', 'moveText'], ['TD', 'setLeadingMoveText'], ['Tm', 'setTextMatrix'], ['T*', 'nextLine'], ['Tj', 'showText'], ['TJ', 'showSpacedText'], ["'", 'nextLineShowText'], ['"', 'nextLineSetSpacingShowText'], ['d0', 'setCharWidth'], ['d1', 'setCharWidthAndBounds'], ['CS', 'setStrokeColorSpace'], ['cs', 'setFillColorSpace'], ['SC', 'setStrokeColor'], ['SCN', 'setStrokeColorN'], ['sc', 'setFillColor'], ['scn', 'setFillColorN'], ['G', 'setStrokeGray'], ['g', 'setFillGray'], ['RG', 'setStrokeRGBColor'], ['rg', 'setFillRGBColor'], ['K', 'setStrokeCMYKColor'], ['k', 'setFillCMYKColor'], ['sh', 'shadingFill'], ['BI', 'beginInlineImage'], ['ID', 'beginImageData'], ['EI', 'endInlineImage'], ['Do', 'paintXObject'], ['MP', 'markPoint'], ['DP', 'markPointProps'], ['BMC', 'beginMarkedContent'], ['BDC', 'beginMarkedContentProps'], ['EMC', 'endMarkedContent'], ['BX', 'beginCompat'], ['EX', 'endCompat']
])

class BaseFontMetricsResult{
  defaultWidth: number;
  widths:Map<string,number>;
  constructor(defaultWidth: number, widths: Map<string,number>) {
    this.defaultWidth = defaultWidth;
    this.widths = widths;
  }
}
class FontTranslationResult {
  name: string;
  dict?: Dict | null;
  properties?: FontProperties | null;
  file?: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
  constructor(name: string, dict: Dict | null = null, properties: FontProperties | null = null, file: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = null) {
    this.name = name;
    this.dict = dict;
    this.properties = properties;
    this.file = file;
  }
}
class PartialEvaluator {
  state: EvalState;
  xref: XRef;
  handler: MessageHandler;
  uniquePrefix: string;
  objIdCounter: number;

  constructor(xref: XRef, handler: MessageHandler, uniquePrefix: string) {
    this.state = new EvalState();
    this.xref = xref;
    this.handler = handler;
    this.uniquePrefix = uniquePrefix;
    this.objIdCounter = 0;
  }

  splitCombinedOperations(operations: string): string[] | null {
    for (let i = operations.length - 1; i > 0; i--) {
      let op1: string = operations.substring(0, i);
      let op2: string = operations.substring(i);
      if (OP_MAP.has(op1) && OP_MAP.has(op2)){
        return [op1, op2];
      }
    }
    return null;
  }

  getOperatorList(stream?: Stream, resources?: Dict, dependency?: string[], queue?: OperatorList): OperatorList {

    let xref: XRef = this.xref;
    let handler: MessageHandler = this.handler;
    let uniquePrefix: string = this.uniquePrefix ?? '';
    if (queue === null || queue === undefined){
      queue = new OperatorList();
    }
    if (queue.argsArray === null || queue.argsArray === undefined){
      queue.argsArray = new Array<(object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[]>();
    }
    if (queue.fnArray === null || queue.fnArray === undefined){
      queue.fnArray = new Array<(object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[]>();
    }

    let args: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = new Array<object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>();


    let insertDependency = (depList: string[]):void => {
      queue.fnArray.push('dependency');
      queue.argsArray.push(depList);
      for (let i = 0; i < depList.length; i++) {
        let dep: string = depList[i];
        if (!dependency.includes(dep)) {
          dependency.push(depList[i]);
        }
      }
    };

    let handleSetFont = (fontName?: string, font?: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): string => {
      let loadedName: string = null;

      let fontRes: Dict = resources.get('Font') as Dict;

      assert(fontRes !== null, 'fontRes not available');

      let fontTemp = font;
      if (xref.fetchIfRef(font) !== null && xref.fetchIfRef(font) !== undefined){
        fontTemp = xref.fetchIfRef(font);
      } else {
        fontTemp = fontRes.get(fontName);
      }
      assertWellFormed(isDict(fontTemp));

      this.objIdCounter += 1;

      font = fontTemp as Dict;
      if ((font as Dict).translated === null || (font as Dict).translated === undefined) {
        (font as Dict).translated = this.translateFont(font as Dict, xref, resources, dependency);
        if ((font as Dict).translated !== null && (font as Dict).translated !== undefined) {
          loadedName = 'font_' + uniquePrefix + this.objIdCounter;
          (font as Dict).translated.properties.loadedName = loadedName;
          (font as Dict).loadedName = loadedName;

          let translated: FontTranslationResult | null = (font as Dict).translated;
          if (translated.file !== null && translated.file !== undefined){
            translated.file = (translated.file as Stream).getBytes();
          }
          if (translated.properties.file !== null && translated.properties.file !== undefined){
            translated.properties.file = (translated.properties.file as Stream).getBytes();
          }
          handler.send("obj", [
            loadedName, 'Font', translated.name, translated.file, translated.properties
          ]);


        }
      }
      if (loadedName === null || loadedName === undefined){
        loadedName = (font as Dict).loadedName;
      }

      insertDependency([loadedName]);
      return loadedName;
    };

    let buildPaintImageXObject = (image: JpegStream, inline: boolean):void => {
      let dict: Dict = image.dict;
      let w: number = dict.get('Width', 'W') as number;
      let h: number = dict.get('Height', 'H') as number;

      let fn:string = '';

      let imageMask: boolean = dict.get('ImageMask', 'IM') as boolean ?? false;

      if (imageMask) {

        let width: number = dict.get('Width', 'W') as number;
        let height: number = dict.get('Height', 'H') as number;
        let bitStrideLength: number = (width + Number_7) >> Number_3;
        let imgArray: Uint8Array = image.getBytes(bitStrideLength * height);
        let decode: number[] = dict.get('Decode', 'D') as number[];
        let inverseDecode:boolean = !!decode && decode[0] > 0;

        fn = 'paintImageMaskXObject';
        args = [imgArray, inverseDecode, width, height];
        return;
      }

      this.objIdCounter += 1;

      let objId: string = 'img_' + uniquePrefix + String(this.objIdCounter);
      insertDependency([objId]);
      args = [objId, w, h];

      let softMask:boolean = dict.get('SMask', 'IM') as boolean || false;

      if (!softMask && image instanceof JpegStream &&
      image.isNativelySupported(xref, resources)) {
        fn = 'paintJpegXObject';
        handler.send("obj", [objId, 'JpegStream', image.getIR()]);
        return;
      }

      fn = 'paintImageXObject';

      PDFImage.buildImage((imageObj) => {
        let drawWidth: number = imageObj.drawWidth;
        let drawHeight: number = imageObj.drawHeight;
        let imgData: Image = new Image()
        imgData.width = drawWidth;
        imgData.height = drawHeight;
        imgData.data = new Uint8Array(drawWidth * drawHeight * Number_4);

        let pixels: Uint8Array = imgData.data;
        imageObj.fillRgbaBuffer(pixels, drawWidth, drawHeight);
        handler.send("obj", [objId, 'Image', imgData]);

      }, handler, xref, resources, image, inline);
    };

    let dependencyArray: string[] = dependency || new Array<string>();

    resources = resources ?? new Dict();
    let xobjs:Dict = resources.get('XObject') as Dict || new Dict();
    let patterns:Dict = resources.get('Pattern') as Dict || new Dict();

    let parser:Parser = new Parser(new Lexer(stream), false, xref);
    let res:Dict = resources;
    let hasNextObj:boolean = false;
    let nextObj: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
    let obj: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
    let TILING_PATTERN: number = 1;
    let SHADING_PATTERN: number = Number_2;
    while (true) {
      if (hasNextObj) {
        obj = nextObj;
        hasNextObj = false;
      } else {
        obj = parser.getObj();
        if (isEOF(obj)){
          break;
        }
      }

      if (isCmd(obj)) {
        let cmd: string = (obj as Cmd).cmd;
        let fn: string = OP_MAP.get(cmd);
        if (fn === null || fn === undefined){
          let cmds: string[] | null = this.splitCombinedOperations(cmd);
          if (cmds !== null && cmds !== undefined){
            cmd = cmds[0];
            fn = OP_MAP.get(cmd);
	    
            hasNextObj = true;
            nextObj = Cmd.get(cmds[1]);
          }
        }
        assertWellFormed(fn !== null && fn !== undefined, `Unknown command "${cmd}"`);

        if ((cmd === 'SCN' || cmd === 'scn') && !(args[args.length - 1] as GlyphsStruct).code) {

          let patternName: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = args[args.length - 1];

          if (isName(patternName)) {
            let pattern: Dict|Stream = (patterns as Dict).get((patternName as Name).name) as Dict|Stream;
            if (pattern !== null && pattern !== undefined){
              let dict: Dict = isStream(pattern) ? (pattern as Stream).dict : (pattern as Dict);
              let typeNum: number = dict.get('PatternType') as number;

              if (typeNum == TILING_PATTERN) {

                let depIdx: number = dependencyArray.length;

                let dictResources: Dict = dict.get("Resources") as Dict;

                let resourcesList: Dict;
                if (dictResources === null || dictResources === undefined){
                  resourcesList = resources;
                }else{
                  resourcesList = dictResources!;
                }
                let operatorList: OperatorList = this.getOperatorList((pattern as Stream),
                  resourcesList  , dependencyArray);
                insertDependency(dependencyArray.slice(depIdx));
                args = TilingPattern.getIR(operatorList, dict, args);

              } else if (typeNum == SHADING_PATTERN) {
                let shading = (dict as Dict).get('Shading');
                let matrix = (dict as Dict).get('Matrix');

                let pattern: Shadings.RadialAxial | Shadings.Dummy = Pattern.parseShading(shading, matrix, xref,
                  res) as Shadings.RadialAxial | Shadings.Dummy;
                if (pattern instanceof Shadings.RadialAxial){
                  args = (pattern as Shadings.RadialAxial).getIR();
                }else {
                  args = (pattern as Shadings.Dummy).getIR();
                }
              } else {
                error(`Unknown PatternType ${typeNum}`);
              }

            }


          }
        } else if (cmd === 'Do' && !(args[0] as GlyphsStruct).code) {

          let name: string = (args[0] as GlyphsStruct).name;
          let xobj: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = xobjs.get(name);
          if (xobj !== null && xobj !== undefined){
            assertWellFormed(isStream(xobj), 'XObject should be a stream');

            let type: Name = (xobj as Stream).dict.get('Subtype') as Name;
            assertWellFormed(isName(type), 'XObject should have a Name subtype');

            if ('Form' === type.name) {
              let matrix: string = (xobj as Stream).dict.get('Matrix') as string;
              let bbox: string = (xobj as Stream).dict.get('BBox') as string;

              queue.fnArray.push('paintFormXObjectBegin');
              queue.argsArray.push([matrix, bbox]);

              let depIdx: number = dependencyArray.length;

              this.getOperatorList(xobj as Stream,
                (xobj as Stream).dict.get('Resources') as Dict || resources,
                dependencyArray, queue);

              insertDependency(dependencyArray.slice(depIdx));
              fn = 'paintFormXObjectEnd';
              args = new Array<object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>();

            }
            else if (type.name === 'Image') {
              buildPaintImageXObject(xobj as JpegStream, false);

            } else {
              error(`Unhandled XObject subtype ${type.name}`);
            }


          }
        } else if (cmd === 'Tf') {

          args[0] = handleSetFont((args[0] as Name).name);

        } else if (cmd === 'EI') {
          buildPaintImageXObject((args[0] as JpegStream), true);
        }

        switch (fn) {
          case 'setFillColorSpace':
          case 'setStrokeColorSpace':
            args = [ColorSpace.parseToIR(args[0], xref, resources)];
            break;
          case 'shadingFill':
            let shadingRes: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = res.get('Shading');
            if (shadingRes === null || shadingRes === undefined){
              error('No shading resource found');
            }
            let shading = (shadingRes as Dict).get((args[0] as GlyphsStruct).name);
            if (shading === null || shading === undefined){
              error('No shading object found');
            }
            let shadingFill:Shadings.RadialAxial | Shadings.Dummy = Pattern.parseShading(shading, null, xref, res) as Shadings.RadialAxial | Shadings.Dummy;
            let patternIR: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[]

            if (shadingFill instanceof Shadings.RadialAxial ){
              patternIR = (shadingFill as Shadings.RadialAxial).getIR();
            }else {
              patternIR = (shadingFill as Shadings.Dummy).getIR();
            }
            args = [patternIR];

            fn = 'shadingFill';
            break;
          case 'setGState':
            let dictName: GlyphsStruct = args[0] as GlyphsStruct;
            let extGState: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = resources.get('ExtGState');

            if (!isDict(extGState) || !(extGState as Dict).has(dictName.name)){
              break;
            }

            let gsState: Dict = (extGState as Dict).get(dictName.name) as Dict;


            let gsStateObj: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[][] = new Array<(object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[]>();

            (gsState as Dict).forEach((key, value)=> {
              switch (key) {
                case 'Type':
                  break;
                case 'LW':
                case 'LC':
                case 'LJ':
                case 'ML':
                case 'D':
                case 'RI':
                case 'FL':
                case 'CA':
                case 'ca':
                  gsStateObj.push([key, value]);
                  break;
                case 'Font':
                  gsStateObj.push(['Font', handleSetFont(null, value[0]), value[1]]);
                  break;
                case 'OP':
                case 'op':
                case 'OPM':
                case 'BG':
                case 'BG2':
                case 'UCR':
                case 'UCR2':
                case 'TR':
                case 'TR2':
                case 'HT':
                case 'SM':
                case 'SA':
                case 'BM':
                case 'SMask':
                case 'AIS':
                case 'TK':
                  TODO(`graphic state operator ${key}`);
                  break;
                default:
                  warn(`Unknown graphic state operator ${key}`);
                  break;
              }
            }
            );
            args = [gsStateObj];
            break;
          default:
            break;
        }

        queue.fnArray.push(fn);
        queue.argsArray.push(args);

        args = new Array<object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>();
      } else if (obj !== null && obj !== undefined){
        assertWellFormed(args.length <= 33, 'Too many arguments');
        if (obj instanceof Dict) {
          args.push(obj.getAll());
        } else {
          args.push(obj);
        }
      }
    }

    return queue;
  }

  extractDataStructures(dict: Dict, baseDict: Dict, xref: XRef, properties: FontProperties):void {

    let toUnicode:Name | Stream  = dict.get('ToUnicode') as Name | Stream || baseDict.get('ToUnicode') as Name | Stream;
    if (toUnicode !== null && toUnicode !== undefined){
      properties.toUnicode = this.readToUnicode(toUnicode, xref, properties);
    }

    if (properties.composite) {

      let cidSystemInfo: Dict = dict.get('CIDSystemInfo') as Dict;
      if (isDict(cidSystemInfo)) {
        properties.cidSystemInfo = {
          registry: cidSystemInfo.get('Registry') as string,
          ordering: cidSystemInfo.get('Ordering') as string,
          supplement: cidSystemInfo.get('Supplement')
        };
      }

      let cidToGidMap: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = dict.get('CIDToGIDMap');
      if (isStream(cidToGidMap)) {
        properties.cidToGidMap = this.readCidToGidMap(cidToGidMap as Stream);
      }
    }

    let flags: number = properties.flags;
    let differences: string[] = new Array<string>();
    let baseEncoding: string[] = (flags & FontFlags.Symbolic) !== 0 ? Encodings.get('symbolsEncoding') : Encodings.get('StandardEncoding');
    let hasEncoding: boolean = dict.has('Encoding');

    if (hasEncoding) {
      let encoding: Dict|Name = dict.get('Encoding') as Dict|Name;
      if (isDict(encoding)) {
        let baseName: Dict|Name  = (encoding as Dict).get('BaseEncoding') as Dict|Name;
        if (baseName !== null && baseName !== undefined){
          baseEncoding = Encodings.get((baseName as Name).name);
        } else {
          hasEncoding = false;
        }

        if ((encoding as Dict).has('Differences')) {

          let diffEncoding: (number|string|Name)[]  = (encoding as Dict).get('Differences') as (number|string|Name)[];
          let index: number = 0;
          for (let j = 0 ;j < diffEncoding.length; j++) {
            let data: number|string|Name = diffEncoding[j];
            if (isNum(data)) {
              index = (data as number);
            } else {
              differences[index] = (data as Name).name;
              index += 1 ;
            }
          }
        }
      } else if (isName(encoding)) {
        baseEncoding = Encodings.get((encoding as Name).name) ?? baseEncoding;
      } else {
        error('Encoding is not a Name nor a Dict');
      }
    }

    properties.differences = differences;
    properties.baseEncoding = baseEncoding;
    properties.hasEncoding = hasEncoding;
  }

  readToUnicode(toUnicode?: Name | Stream, xref?: XRef, properties?: FontProperties): number[] {
    let cmapObj: Name | Stream = toUnicode;
    let charToUnicode: number[] = new Array<number>();

    if (isName(cmapObj)) {
      let isIdentityMap: boolean = (cmapObj as Name).name.substring(0, Number_9) == 'Identity-';
      if (!isIdentityMap){
        error('ToUnicode file cmap translation not implemented');
      }
    } else if (isStream(cmapObj)) {
      let tokens: (string|number|(string|number)[])[] = new Array<string | number | (string | number)[]>();
      let token: string = '';
      let beginArrayToken:string | null = null;

      let cmap: Uint8Array = (cmapObj as Stream).getBytes((cmapObj as Stream).length);
      for (let i = 0; i < cmap.length; i++) {
        let octet: number = cmap[i];
        if (octet === Number_0x20 || octet === Number_0x0D || octet === Number_0x0A ||
          octet === Number_0x3C || octet === Number_0x5B || octet === Number_0x5D) {
          switch (token) {
            case 'usecmap':
              error('usecmap is not implemented');
              break;
            case 'beginbfchar':
            case 'beginbfrange':
            case 'begincidchar':
            case 'begincidrange':
              token = '';
              tokens = new Array<string | number | (string | number)[]>();
              break;
            case 'endcidrange':
            case 'endbfrange':
              for (let j = 0; j < tokens.length; j += Number_3) {
                let startRange: number = tokens[j] as number;
                let endRange: number = tokens[j + 1] as number;
                let code: number = tokens[j + Number_2] as number;

                if (code == Number_0xFFFF) {

                  charToUnicode[startRange] = code;
                }

                if (isArray(code)) {
                  let codeindex: number = 0;
                  while (startRange <= endRange) {
                    charToUnicode[startRange] = code[codeindex];
                    startRange += 1;
                    codeindex += 1;
                  }
                } else {
                  while (startRange <= endRange) {
                    charToUnicode[startRange] = code;
                    startRange += 1;
                    code += 1;
                  }
                }

              }
              break;

            case 'endcidchar':
            case 'endbfchar':
              let j:number = 0;
              while ( j < tokens.length) {
                let index: number = tokens[j] as number;
                let code: number = tokens[j + 1] as number;
                charToUnicode[index] = code;
                j += Number_2;
              }
              break;

            case '':
              break;

            default:
              if (token[0] >= '0' && token[0] <= '9'){
                let tokenInt:number = Number.parseInt(token,Number_10);
                token = "";
                tokens.push(tokenInt);
              } else {
                tokens.push(token);
                token = '';
              }

              break;
          }
          switch (octet) {
            case Number_0x5B:

              tokens.push(beginArrayToken);
              break;
            case Number_0x5D:

              let items: (string|number)[] = new Array<string | number>();
              let item:string|number;
              item = tokens.pop() as string;
              while (tokens.length > 0 && (item !== beginArrayToken)) {
                items.unshift(item);
                item = tokens.pop() as string;
              }
              tokens.push(items);
              break;
          }
        } else if (octet == Number_0x3E) {
          if (token.length > 0) {

            if (token.length <= Number_2 && properties.composite) {
              properties.wideChars = false;
            }
            if (token.length <= Number_4) {

              tokens.push(Number.parseInt(token, Number_16));
              token = '';
            } else {

              let str: number[] = new Array<number>();
              let k:number = 0
              while ( k < token.length) {
                let b: number = Number.parseInt(token.substring(k, Number_4), Number_16);
                if (b <= Number_0x10) {
                  k += Number_4;
                  b = (b << Number_16) | Number.parseInt(token.substring(k, Number_4));
                  b -= Number_0x10000;
                  str.push(0xD800 | (b >> Number_10));
                  str.push(0xDC00 | (b & Number_0x3FF));
                  break;
                }
                str.push(b);
                k += Number_4;
              }
              tokens.push(String.fromCharCode.apply(String, str));
              token = '';
            }
          }
        }
        else {
          token += String.fromCharCode(octet);
        }
      }
    }

    return charToUnicode;
  }

  readCidToGidMap(cidToGidStream: Stream): number[] {

    let glyphsData: Uint8Array = cidToGidStream.getBytes();


    let result: number[] = new Array<number>();
    let j:number = 0;
    while (j < glyphsData.length) {
      let glyphID: number = (glyphsData[j] << Number_8) | glyphsData[j+1];
      j += 1;
      if (glyphID == 0) {
        continue;
      }
      let code: number = j >> 1;
      result[code] = glyphID;
      j += 1;
    }
    
    return result;
  }

  extractWidths(dict: Dict, xref: XRef, descriptor: Dict, properties: FontProperties): void {
    let glyphsWidths: number[]| Map<string, number> = new Array<number>();
    let defaultWidth: number = 0.0;

    if (properties.composite) {
      defaultWidth = dict.get('DW') as number || 1000;

      let widths: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = dict.get('W') as (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[];//string|[]
      if (widths !== null && widths !== undefined){
        let start: number = 0;
        let end: number = 0;
        for (let i = 0; i < widths.length; i++) {
          let code: number|number[] = widths[i] as  number|number[];
          if (isArray(code)) {
            for (let j = 0; j < (code as number[]).length; j++) {
              let glyphsWidthsTemp: number[] = glyphsWidths as number[];
              glyphsWidthsTemp[start] = code[j];
              start += 1;
            }
            start = 0;
          }
          else if (start !== 0) {
            let width: number = widths[++i] as number;
            for (let j = start; j <= code; j++){
              let glyphsWidthsTemp: number[] = glyphsWidths as number[];
              glyphsWidthsTemp[j] = width;

            }
            start = 0;
          } else {
            start = code as number;
          }
        }
      }
    } else {
      let firstChar: number = properties.firstChar;
      let widths: number[] = dict.get('Widths') as number[];
      if (widths !== null && widths !== undefined){
        let j: number = firstChar;
        for (let i = 0; i < widths.length; i++) {
          let glyphsWidthsTemp: number[] = glyphsWidths as number[];
          glyphsWidthsTemp[j] = widths[i];
          j += 1;
        }
        defaultWidth = Number.parseFloat(descriptor.get('MissingWidth') as string) || 0;
      } else {

        let baseFontName: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = dict.get('BaseFont');
        if (isName(baseFontName)) {
          let metrics: BaseFontMetricsResult = this.getBaseFontMetrics((baseFontName as Name).name);
          glyphsWidths = metrics.widths;
          defaultWidth = metrics.defaultWidth;
        }
      }
    }
    properties.defaultWidth = defaultWidth;
    properties.widths = glyphsWidths;
  }


  getBaseFontMetrics(name: string): BaseFontMetricsResult {
    let defaultWidth: number = 0;
    let widths: Map<string,number> = new Map();

    let glyphWidths: Map<string,number>|number = Metrics.get(stdFontMap.get(name) || name);
    if (glyphWidths === null || glyphWidths === undefined){
      if (isNum(glyphWidths)) {
        defaultWidth = (glyphWidths as number);
      } else {
        widths = (glyphWidths as Map<string,number>);
      }
    }

    return new BaseFontMetricsResult(defaultWidth,widths);
  }

  translateFont(dict: Dict, xref: XRef, resources: Dict, dependency: string[]): FontTranslationResult {
    let baseDict: Dict = dict;
    let type: Name = dict.get('Subtype') as Name;

    assertWellFormed(isName(type), 'invalid font Subtype');

    let composite: boolean = false;

    if (type.name == 'Type0') {

      let df: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = dict.get('DescendantFonts');
      if (df === null || df === undefined){
        return null;
      }
      dict = isArray(df) ? xref.fetchIfRef(df[0]) as Dict : df as Dict;

      type = dict.get('Subtype') as Name;

      assertWellFormed(isName(type), 'invalid font Subtype');

      composite = true;
    }

    let maxCharIndex: number = composite ? Number_0xFFFF : Number_0xFF;

    let descriptor: Dict = dict.get('FontDescriptor') as Dict;

    if (descriptor === null) {
      if (type.name === 'Type3') {


        descriptor = new Dict();
        descriptor.set('FontName', new Name(type.name));

      } else {
        let baseFontName: string | Name = dict.get('BaseFont') as string | Name;
        if (!isName(baseFontName)) {
          return null;
        }

        let baseFontNameStr: string = (baseFontName as Name).name.replace(new RegExp('/[,_]/','g'), '-');
        let metrics: BaseFontMetricsResult = this.getBaseFontMetrics(baseFontNameStr);

        let fontNameWoStyle: string = baseFontNameStr.split('-')[0];

        let boolFrist: number = serifFonts.get(fontNameWoStyle) || (fontNameWoStyle.search(new RegExp('/serif/','gi')) !== -1) ? FontFlags.Serif : 0;
        let boolSecond: number = symbolsFonts.get(fontNameWoStyle) ? FontFlags.Symbolic : FontFlags.Nonsymbolic;

        let flags: number = boolFrist | boolSecond

        let properties: FontProperties = new FontProperties()
        properties.type = type.name
        properties.firstChar = 0
        properties.lastChar = maxCharIndex
        properties.flags = flags
        properties.defaultWidth = metrics.defaultWidth
        properties.widths = metrics.widths


        this.extractDataStructures(dict, dict, xref, properties);

        return new FontTranslationResult(
          baseFontNameStr,
          baseDict,
          properties
        );
      }
    }

    let firstChar: number = dict.get('FirstChar') as number || 0;
    let lastChar: number = dict.get('LastChar') as number || maxCharIndex;
    let fontName: Name | string = descriptor.get('FontName') as Name | string ;

    if (isString(fontName)) {
      fontName = new Name(fontName as string);
    }
    assertWellFormed(isName(fontName), 'invalid font name');

    let fontFile: FlateStream = descriptor.get('FontFile', 'FontFile2', 'FontFile3') as FlateStream;

    let subtype: string;
    let length1: number;
    let length2: number;
    if (fontFile !== null && fontFile !== undefined){
      if (fontFile.dict !== null && fontFile.dict !== undefined){
        let subtypeTemp: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = fontFile.dict.get('Subtype');
        if (subtype !== null && subtype !== undefined){
          subtype = (subtypeTemp as Name).name;
        }
        length1 = fontFile.dict.get('Length1') as number;
        length2 = fontFile.dict.get('Length2') as number;

      }
    }


    let properties: FontProperties = new FontProperties()
    properties.type = type.name,
    properties.subtype= subtype ?? "",
    properties.file= fontFile,
    properties.length1= length1,
    properties.length2= length2,
    properties.composite= composite,
    properties.wideChars= composite,
    properties.fixedPitch= false,
    properties.fontMatrix= dict.get("FontMatrix") as number[] ?? IDENTITY_MATRIX,
    properties.firstChar= firstChar ,
    properties.lastChar= lastChar ,
    properties.bbox= (descriptor as Dict).get("FontBBox") as number[],
    properties.ascent= ((descriptor as Dict).get("Ascent") as number),
    properties.descent= ((descriptor as Dict).get("Descent") as number),
    properties.xHeight= ((descriptor as Dict).get("XHeight") as number),
    properties.capHeight= ((descriptor as Dict).get("CapHeight") as number),
    properties.flags= ((descriptor as Dict).get("Flags") as number),
    properties.italicAngle= ((descriptor as Dict).get("ItalicAngle") as number),
    properties.coded= false


    this.extractWidths(dict, xref, descriptor, properties);
    this.extractDataStructures(dict, baseDict, xref, properties);

    if (type.name === 'Type3') {
      properties.coded = true;
      let charProcs: Map<string, string | object | number | boolean> = (dict.get('CharProcs') as Dict).getAll();
      let fontResources: Dict = dict.get('Resources') as Dict || resources;

      let charProcOperatorList: Map<string, OperatorList> = new Map<string, OperatorList>();

      for (let [key, value] of charProcs) {

        charProcOperatorList.set(key,this.getOperatorList(value as Stream, fontResources as Dict, dependency));

      }

      properties.charProcOperatorList = charProcOperatorList;
    }

    return new FontTranslationResult (
      (fontName as Name).name,
      baseDict,
      properties,
      fontFile
    );
  }

}

let kMaxWaitForFontFace: number = 1000;
let kCmapGlyphOffset:number = 0xE000;
let kSizeOfGlyphArea:number = 0x1900;
let kSymbolicFontGlyphOffset:number = 0xF000;
let kPDFGlyphSpaceUnits:number = 1000;
let kHintingEnabled:boolean = false;


class FontFlags {
  static FixedPitch: number = 1;
  static Serif: number = Number_2;
  static Symbolic: number = Number_4;
  static Script: number = Number_8;
  static Nonsymbolic: number = Number_32;
  static Italic: number = Number_64;
  static AllCap: number = 65536;
  static SmallCap: number = 131072;
  static ForceBold: number = 262144;

}


let Encodings = new Map<string,string[]>([
  ['ExpertEncoding',['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior', '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters', 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall']], ['MacExpertEncoding',['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', 'centoldstyle', 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon', '', 'threequartersemdash', '', 'questionsmall', '', '', '', '', 'Ethsmall', '', '', 'onequarter', 'onehalf', 'threequarters', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', '', '', '', '', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hypheninferior', 'Gravesmall', 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', 'asuperior', 'centsuperior', '', '', '', '', 'Aacutesmall', 'Agravesmall', 'Acircumflexsmall', 'Adieresissmall', 'Atildesmall', 'Aringsmall', 'Ccedillasmall', 'Eacutesmall', 'Egravesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Iacutesmall', 'Igravesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ntildesmall', 'Oacutesmall', 'Ogravesmall', 'Ocircumflexsmall', 'Odieresissmall', 'Otildesmall', 'Uacutesmall', 'Ugravesmall', 'Ucircumflexsmall', 'Udieresissmall', '', 'eightsuperior', 'fourinferior', 'threeinferior', 'sixinferior', 'eightinferior', 'seveninferior', 'Scaronsmall', '', 'centinferior', 'twoinferior', '', 'Dieresissmall', '', 'Caronsmall', 'osuperior', 'fiveinferior', '', 'commainferior', 'periodinferior', 'Yacutesmall', '', 'dollarinferior', '', 'Thornsmall', '', 'nineinferior', 'zeroinferior', 'Zcaronsmall', 'AEsmall', 'Oslashsmall', 'questiondownsmall', 'oneinferior', 'Lslashsmall', '', '', '', '', '', '', 'Cedillasmall', '', '', '', '', '', 'OEsmall', 'figuredash', 'hyphensuperior', '', '', '', '', 'exclamdownsmall', '', 'Ydieresissmall', '', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'ninesuperior', 'zerosuperior', '', 'esuperior', 'rsuperior', 'tsuperior', '', '', 'isuperior', 'ssuperior', 'dsuperior', '', '', '', '', '', 'lsuperior', 'Ogoneksmall', 'Brevesmall', 'Macronsmall', 'bsuperior', 'nsuperior', 'msuperior', 'commasuperior', 'periodsuperior', 'Dotaccentsmall', 'Ringsmall']], ['MacRomanEncoding',['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', 'ellipsis', '', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex', 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron']], ['StandardEncoding', ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '', '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', 'lslash', 'oslash', 'oe', 'germandbls']], ['WinAnsiEncoding',['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'bullet', 'Euro', 'bullet', 'quotesinglbase', 'florin', 'quotedblbase', 'ellipsis', 'dagger', 'daggerdbl', 'circumflex', 'perthousand', 'Scaron', 'guilsinglleft', 'OE', 'bullet', 'Zcaron', 'bullet', 'bullet', 'quoteleft', 'quoteright', 'quotedblleft', 'quotedblright', 'bullet', 'endash', 'emdash', 'tilde', 'trademark', 'scaron', 'guilsinglright', 'oe', 'bullet', 'zcaron', 'Ydieresis', '', 'exclamdown', 'cent', 'sterling', 'currency', 'yen', 'brokenbar', 'section', 'dieresis', 'copyright', 'ordfeminine', 'guillemotleft', 'logicalnot', 'hyphen', 'registered', 'macron', 'degree', 'plusminus', 'twosuperior', 'threesuperior', 'acute', 'mu', 'paragraph', 'periodcentered', 'cedilla', 'onesuperior', 'ordmasculine', 'guillemotright', 'onequarter', 'onehalf', 'threequarters', 'questiondown', 'Agrave', 'Aacute', 'Acircumflex', 'Atilde', 'Adieresis', 'Aring', 'AE', 'Ccedilla', 'Egrave', 'Eacute', 'Ecircumflex', 'Edieresis', 'Igrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Eth', 'Ntilde', 'Ograve', 'Oacute', 'Ocircumflex', 'Otilde', 'Odieresis', 'multiply', 'Oslash', 'Ugrave', 'Uacute', 'Ucircumflex', 'Udieresis', 'Yacute', 'Thorn', 'germandbls', 'agrave', 'aacute', 'acircumflex', 'atilde', 'adieresis', 'aring', 'ae', 'ccedilla', 'egrave', 'eacute', 'ecircumflex', 'edieresis', 'igrave', 'iacute', 'icircumflex', 'idieresis', 'eth', 'ntilde', 'ograve', 'oacute', 'ocircumflex', 'otilde', 'odieresis', 'divide', 'oslash', 'ugrave', 'uacute', 'ucircumflex', 'udieresis', 'yacute', 'thorn', 'ydieresis']], ['symbolsEncoding',['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'space', 'exclam', 'universal', 'numbersign', 'existential', 'percent', 'ampersand', 'suchthat', 'parenleft', 'parenright', 'asteriskmath', 'plus', 'comma', 'minus', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question', 'congruent', 'Alpha', 'Beta', 'Chi', 'Delta', 'Epsilon', 'Phi', 'Gamma', 'Eta', 'Iota', 'theta1', 'Kappa', 'Lambda', 'Mu', 'Nu', 'Omicron', 'Pi', 'Theta', 'Rho', 'Sigma', 'Tau', 'Upsilon', 'sigma1', 'Omega', 'Xi', 'Psi', 'Zeta', 'bracketleft', 'therefore', 'bracketright', 'perpendicular', 'underscore', 'radicalex', 'alpha', 'beta', 'chi', 'delta', 'epsilon', 'phi', 'gamma', 'eta', 'iota', 'phi1', 'kappa', 'lambda', 'mu', 'nu', 'omicron', 'pi', 'theta', 'rho', 'sigma', 'tau', 'upsilon', 'omega1', 'omega', 'xi', 'psi', 'zeta', 'braceleft', 'bar', 'braceright', 'similar', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'Euro', 'Upsilon1', 'minute', 'lessequal', 'fraction', 'infinity', 'florin', 'club', 'diamond', 'heart', 'spade', 'arrowboth', 'arrowleft', 'arrowup', 'arrowright', 'arrowdown', 'degree', 'plusminus', 'second', 'greaterequal', 'multiply', 'proportional', 'partialdiff', 'bullet', 'divide', 'notequal', 'equivalence', 'approxequal', 'ellipsis', 'arrowvertex', 'arrowhorizex', 'carriagereturn', 'aleph', 'Ifraktur', 'Rfraktur', 'weierstrass', 'circlemultiply', 'circleplus', 'emptyset', 'intersection', 'union', 'propersuperset', 'reflexsuperset', 'notsubset', 'propersubset', 'reflexsubset', 'element', 'notelement', 'angle', 'gradient', 'registerserif', 'copyrightserif', 'trademarkserif', 'product', 'radical', 'dotmath', 'logicalnot', 'logicaland', 'logicalor', 'arrowdblboth', 'arrowdblleft', 'arrowdblup', 'arrowdblright', 'arrowdbldown', 'lozenge', 'angleleft', 'registersans', 'copyrightsans', 'trademarksans', 'summation', 'parenlefttp', 'parenleftex', 'parenleftbt', 'bracketlefttp', 'bracketleftex', 'bracketleftbt', 'bracelefttp', 'braceleftmid', 'braceleftbt', 'braceex', '', 'angleright', 'integral', 'integraltp', 'integralex', 'integralbt', 'parenrighttp', 'parenrightex', 'parenrightbt', 'bracketrighttp', 'bracketrightex', 'bracketrightbt', 'bracerighttp', 'bracerightmid', 'bracerightbt']], ['zapfDingbatsEncoding',['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'space', 'a1', 'a2', 'a202', 'a3', 'a4', 'a5', 'a119', 'a118', 'a117', 'a11', 'a12', 'a13', 'a14', 'a15', 'a16', 'a105', 'a17', 'a18', 'a19', 'a20', 'a21', 'a22', 'a23', 'a24', 'a25', 'a26', 'a27', 'a28', 'a6', 'a7', 'a8', 'a9', 'a10', 'a29', 'a30', 'a31', 'a32', 'a33', 'a34', 'a35', 'a36', 'a37', 'a38', 'a39', 'a40', 'a41', 'a42', 'a43', 'a44', 'a45', 'a46', 'a47', 'a48', 'a49', 'a50', 'a51', 'a52', 'a53', 'a54', 'a55', 'a56', 'a57', 'a58', 'a59', 'a60', 'a61', 'a62', 'a63', 'a64', 'a65', 'a66', 'a67', 'a68', 'a69', 'a70', 'a71', 'a72', 'a73', 'a74', 'a203', 'a75', 'a204', 'a76', 'a77', 'a78', 'a79', 'a81', 'a82', 'a83', 'a84', 'a97', 'a98', 'a99', 'a100', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'a101', 'a102', 'a103', 'a104', 'a106', 'a107', 'a108', 'a112', 'a111', 'a110', 'a109', 'a120', 'a121', 'a122', 'a123', 'a124', 'a125', 'a126', 'a127', 'a128', 'a129', 'a130', 'a131', 'a132', 'a133', 'a134', 'a135', 'a136', 'a137', 'a138', 'a139', 'a140', 'a141', 'a142', 'a143', 'a144', 'a145', 'a146', 'a147', 'a148', 'a149', 'a150', 'a151', 'a152', 'a153', 'a154', 'a155', 'a156', 'a157', 'a158', 'a159', 'a160', 'a161', 'a163', 'a164', 'a196', 'a165', 'a192', 'a166', 'a167', 'a168', 'a169', 'a170', 'a171', 'a172', 'a173', 'a162', 'a174', 'a175', 'a176', 'a177', 'a178', 'a179', 'a193', 'a180', 'a199', 'a181', 'a200', 'a182', '', 'a201', 'a183', 'a184', 'a197', 'a185', 'a194', 'a198', 'a186', 'a195', 'a187', 'a188', 'a189', 'a190', 'a191']]
]);

let stdFontMap = new Map<string, string>([
  ['ArialNarrow', 'Helvetica'], ['ArialNarrow-Bold', 'Helvetica-Bold'], ['ArialNarrow-BoldItalic', 'Helvetica-BoldOblique'], ['ArialNarrow-Italic', 'Helvetica-Oblique'], ['ArialBlack', 'Helvetica'], ['ArialBlack-Bold', 'Helvetica-Bold'], ['ArialBlack-BoldItalic', 'Helvetica-BoldOblique'], ['ArialBlack-Italic', 'Helvetica-Oblique'], ['Arial', 'Helvetica'], ['Arial-Bold', 'Helvetica-Bold'], ['Arial-BoldItalic', 'Helvetica-BoldOblique'], ['Arial-Italic', 'Helvetica-Oblique'], ['Arial-BoldItalicMT', 'Helvetica-BoldOblique'], ['Arial-BoldMT', 'Helvetica-Bold'], ['Arial-ItalicMT', 'Helvetica-Oblique'], ['ArialMT', 'Helvetica'], ['Courier-Bold', 'Courier-Bold'], ['Courier-BoldItalic', 'Courier-BoldOblique'], ['Courier-Italic', 'Courier-Oblique'], ['CourierNew', 'Courier'], ['CourierNew-Bold', 'Courier-Bold'], ['CourierNew-BoldItalic', 'Courier-BoldOblique'], ['CourierNew-Italic', 'Courier-Oblique'], ['CourierNewPS-BoldItalicMT', 'Courier-BoldOblique'], ['CourierNewPS-BoldMT', 'Courier-Bold'], ['CourierNewPS-ItalicMT', 'Courier-Oblique'], ['CourierNewPSMT', 'Courier'], ['Helvetica-Bold', 'Helvetica-Bold'], ['Helvetica-BoldItalic', 'Helvetica-BoldOblique'], ['Helvetica-Italic', 'Helvetica-Oblique'], ['Symbol-Bold', 'Symbol'], ['Symbol-BoldItalic', 'Symbol'], ['Symbol-Italic', 'Symbol'], ['TimesNewRoman', 'Times-Roman'], ['TimesNewRoman-Bold', 'Times-Bold'], ['TimesNewRoman-BoldItalic', 'Times-BoldItalic'], ['TimesNewRoman-Italic', 'Times-Italic'], ['TimesNewRomanPS', 'Times-Roman'], ['TimesNewRomanPS-Bold', 'Times-Bold'], ['TimesNewRomanPS-BoldItalic', 'Times-BoldItalic'], ['TimesNewRomanPS-BoldItalicMT', 'Times-BoldItalic'], ['TimesNewRomanPS-BoldMT', 'Times-Bold'], ['TimesNewRomanPS-Italic', 'Times-Italic'], ['TimesNewRomanPS-ItalicMT', 'Times-Italic'], ['TimesNewRomanPSMT', 'Times-Roman'], ['TimesNewRomanPSMT-Bold', 'Times-Bold'], ['TimesNewRomanPSMT-BoldItalic', 'Times-BoldItalic'], ['TimesNewRomanPSMT-Italic', 'Times-Italic']
]);

let nonStdFontMap = new Map<string, string>([
  ['ComicSansMS', 'Comic Sans MS'], ['ComicSansMS-Bold', 'Comic Sans MS-Bold'], ['ComicSansMS-BoldItalic', 'Comic Sans MS-BoldItalic'], ['ComicSansMS-Italic', 'Comic Sans MS-Italic'], ['LucidaConsole', 'Courier'], ['LucidaConsole-Bold', 'Courier-Bold'], ['LucidaConsole-BoldItalic', 'Courier-BoldOblique'], ['LucidaConsole-Italic', 'Courier-Oblique']
]);

let serifFonts: Map<string, boolean> = new Map([
  ["Adobe Jenson", true], ["Adobe Text", true], ["Albertus", true], ["Aldus", true], ["Alexandria", true], ["Algerian", true], ["American Typewriter", true], ["Antiqua", true], ["Apex", true], ["Arno", true], ["Aster", true], ["Aurora", true], ["Baskerville", true], ["Bell", true], ["Bembo", true], ["Bembo Schoolbook", true], ["Benguiat", true], ["Berkeley Old Style", true], ["Bernhard Modern", true], ["Berthold City", true], ["Bodoni", true], ["Bauer Bodoni", true], ["Book Antiqua", true], ["Bookman", true], ["Bordeaux Roman", true], ["Californian FB", true], ["Calisto", true], ["Calvert", true], ["Capitals", true], ["Cambria", true], ["Cartier", true], ["Caslon", true], ["Catull", true], ["Centaur", true], ["Century Old Style", true], ["Century Schoolbook", true], ["Chaparral", true], ["Charis SIL", true], ["Cheltenham", true], ["Cholla Slab", true], ["Clarendon", true], ["Clearface", true], ["Cochin", true], ["Colonna", true], ["Computer Modern", true], ["Concrete Roman", true], ["Constantia", true], ["Cooper Black", true], ["Corona", true], ["Ecotype", true], ["Egyptienne", true], ["Elephant", true], ["Excelsior", true], ["Fairfield", true], ["FF Scala", true], ["Folkard", true], ["Footlight", true], ["FreeSerif", true], ["Friz Quadrata", true], ["Garamond", true], ["Gentium", true], ["Georgia", true], ["Gloucester", true], ["Goudy Old Style", true], ["Goudy Schoolbook", true], ["Goudy Pro Font", true], ["Granjon", true], ["Guardian Egyptian", true], ["Heather", true], ["Hercules", true], ["High Tower Text", true], ["Hiroshige", true], ["Hoefler Text", true], ["Humana Serif", true], ["Imprint", true], ["Ionic No. 5", true], ["Janson", true], ["Joanna", true], ["Korinna", true], ["Lexicon", true], ["Liberation Serif", true], ["Linux Libertine", true], ["Literaturnaya", true], ["Lucida", true], ["Lucida Bright", true], ["Melior", true], ["Memphis", true], ["Miller", true], ["Minion", true], ["Modern", true], ["Mona Lisa", true], ["Mrs Eaves", true], ["MS Serif", true], ["Museo Slab", true], ["New York", true], ["Nimbus Roman", true], ["NPS Rawlinson Roadway", true], ["Palatino", true], ["Perpetua", true], ["Plantin", true], ["Plantin Schoolbook", true], ["Playbill", true], ["Poor Richard", true], ["Rawlinson Roadway", true], ["Renault", true], ["Requiem", true], ["Rockwell", true], ["Roman", true], ["Rotis Serif", true], ["Sabon", true], ["Scala", true], ["Seagull", true], ["Sistina", true], ["Souvenir", true], ["STIX", true], ["Stone Informal", true], ["Stone Serif", true], ["Sylfaen", true], ["Times", true], ["Trajan", true], ["Trinité", true], ["Trump Mediaeval", true], ["Utopia", true], ["Vale Type", true], ["Bitstream Vera", true], ["Vera Serif", true], ["Versailles", true], ["Wanted", true], ["Weiss", true], ["Wide Latin", true], ["Windsor", true], ["XITS", true]
]);

let symbolsFonts: Map<string, boolean> = new Map([
  ["Dingbats", true], ["Symbol", true], ["ZapfDingbats", true]
]);

function mapPrivateUseChars(code: number): number {
  switch (code) {
    case Number_0xF8E9:
    case Number_0xF6D9:
      return Number_0x00A9;
    default:
      return code;
  }
}

class FontLoader {
  static listeningForFontLoad: boolean = false;

  static loaderBind(fonts:Font[], callback: () => void): void {

    let checkFontsLoaded = (): boolean => {
      for (let i = 0; i < fonts.length; i++) {
        let fontObj: Font = fonts[i];
        if (fontObj.loading) {
          return false;
        }
      }
      PdfJS_window.document.documentElement.removeEventListener('pdfjsFontLoad');

      callback();
      return true;
    }

    let rules: string[] = new Array<string>();
    let names: string[] = new Array<string>();

    let fontsToLoad: Font[] = new Array<Font>();

    let fontCreateTimer: number = 0;

    for (let i = 0; i < fonts.length; i++) {
      let font: Font = fonts[i];


      if (font.attached || font.loading == false) {
        continue;
      }
      font.attached = true;

      fontsToLoad.push(font);

      let str: string = '';
      let data: Uint8Array = font.data;
      if (data !== null && data !== undefined){
        for (let j = 0; j < data.length; j++) {
          str += String.fromCharCode(data[j]);
        }

        let rule: string = font.bindDOM(str);
        if (rule !== null && rule !== undefined){
          rules.push(rule);
          names.push(font.loadedName);
        }
      }
    }

    FontLoader.listeningForFontLoad = false;
    if (!isWorker && rules.length) {
      FontLoader.prepareFontLoadEvent(rules, names, fontsToLoad);
    }

    if (!checkFontsLoaded()) {
      PdfJS_window.document.documentElement.addEventListener('pdfjsFontLoad', checkFontsLoaded,false);
    }

  }

  static prepareFontLoadEvent(rules: string[], names: string[], fonts: Font[]) {

    if (!/^\w+$/.test(names.join(''))) {
      error(`Invalid font name(s): ${names.join()}`);

      return;
    }

    let div:PDFElement = PdfJS_window.document.createElement('div');
    div.setAttribute('style',
      'visibility: hidden;' +
        'width: 10px; height: 10px;' +
        'position: absolute; top: 0px; left: 0px;');
    let html: string = '';
    for (let i = 0; i < names.length; i++) {
      html += `<span style="font-family:${names[i]}">Hi</span>`;
    }
    div.innerHTML = html;
    PdfJS_window.document.body.appendChild(div);

    if (!this.listeningForFontLoad) {
      PdfJS_window.window.addEventListener(
        'message',
        fontLoaderMessage,
        false
      )
      this.listeningForFontLoad = true;
    }

    function fontLoaderMessage():void {
      for (let i = 0; i < fonts.length; i++) {
        let font: Font = fonts[i];
        font.loading = false;
      }
      let evt:PDFEvent = PdfJS_window.document.createEvent('Events');
      evt.initEvent('pdfjsFontLoad',true,false);
      PdfJS_window.document.documentElement.dispatchEvent(evt);
    }

    let src: string = '<!DOCTYPE HTML><html><head>';
    src += '<style type="text/css">';
    for (let i = 0; i < rules.length; ++i) {
      src += rules[i];
    }
    src += '</style>';
    src += '<script type="application/javascript">';
    let fontNamesArray: string = '';
    for (let i = 0; i < names.length; ++i) {
      fontNamesArray += `"${names[i]}", `;
    }
    src += `  var fontNames=[${fontNamesArray}];\n`;
    src += '  PdfJS_window.window.onload = function fontLoaderOnload() {\n';
    src += '    parent.postMessage(JSON.stringify(fontNames), "*");\n';
    src += '  }';

    src += '</scr' + 'ipt></head><body>';
    for (let i = 0; i < names.length; ++i) {
      src += `<p style="font-family:\' ${names[i]} \'">Hi</p>`;
    }
    src += '</body></html>';
    let frame: Canvas | PDFElement = PdfJS_window.document.createElement('iframe');
    (frame as PDFElement).src = 'data:text/html,' + src;
    (frame as PDFElement).setAttribute('style',
      'visibility: hidden;' +
        'width: 10px; height: 10px;' +
        'position: absolute; top: 0px; left: 0px;');
    PdfJS_window.document.body.appendChild(frame);

  }
}

let UnicodeRanges: Record<string, number>[] = [
  { 'begin': 0x0000, 'end': 0x007F }, { 'begin': 0x0080, 'end': 0x00FF }, { 'begin': 0x0100, 'end': 0x017F }, { 'begin': 0x0180, 'end': 0x024F }, { 'begin': 0x0250, 'end': 0x02AF }, { 'begin': 0x02B0, 'end': 0x02FF }, { 'begin': 0x0300, 'end': 0x036F }, { 'begin': 0x0370, 'end': 0x03FF }, { 'begin': 0x2C80, 'end': 0x2CFF }, { 'begin': 0x0400, 'end': 0x04FF }, { 'begin': 0x0530, 'end': 0x058F }, { 'begin': 0x0590, 'end': 0x05FF }, { 'begin': 0xA500, 'end': 0xA63F }, { 'begin': 0x0600, 'end': 0x06FF }, { 'begin': 0x07C0, 'end': 0x07FF }, { 'begin': 0x0900, 'end': 0x097F }, { 'begin': 0x0980, 'end': 0x09FF }, { 'begin': 0x0A00, 'end': 0x0A7F }, { 'begin': 0x0A80, 'end': 0x0AFF }, { 'begin': 0x0B00, 'end': 0x0B7F }, { 'begin': 0x0B80, 'end': 0x0BFF }, { 'begin': 0x0C00, 'end': 0x0C7F }, { 'begin': 0x0C80, 'end': 0x0CFF }, { 'begin': 0x0D00, 'end': 0x0D7F }, { 'begin': 0x0E00, 'end': 0x0E7F }, { 'begin': 0x0E80, 'end': 0x0EFF }, { 'begin': 0x10A0, 'end': 0x10FF }, { 'begin': 0x1B00, 'end': 0x1B7F }, { 'begin': 0x1100, 'end': 0x11FF }, { 'begin': 0x1E00, 'end': 0x1EFF }, { 'begin': 0x1F00, 'end': 0x1FFF }, { 'begin': 0x2000, 'end': 0x206F }, { 'begin': 0x2070, 'end': 0x209F }, { 'begin': 0x20A0, 'end': 0x20CF }, { 'begin': 0x20D0, 'end': 0x20FF }, { 'begin': 0x2100, 'end': 0x214F }, { 'begin': 0x2150, 'end': 0x218F }, { 'begin': 0x2190, 'end': 0x21FF }, { 'begin': 0x2200, 'end': 0x22FF }, { 'begin': 0x2300, 'end': 0x23FF }, { 'begin': 0x2400, 'end': 0x243F }, { 'begin': 0x2440, 'end': 0x245F }, { 'begin': 0x2460, 'end': 0x24FF }, { 'begin': 0x2500, 'end': 0x257F }, { 'begin': 0x2580, 'end': 0x259F }, { 'begin': 0x25A0, 'end': 0x25FF }, { 'begin': 0x2600, 'end': 0x26FF }, { 'begin': 0x2700, 'end': 0x27BF }, { 'begin': 0x3000, 'end': 0x303F }, { 'begin': 0x3040, 'end': 0x309F }, { 'begin': 0x30A0, 'end': 0x30FF }, { 'begin': 0x3100, 'end': 0x312F }, { 'begin': 0x3130, 'end': 0x318F }, { 'begin': 0xA840, 'end': 0xA87F }, { 'begin': 0x3200, 'end': 0x32FF }, { 'begin': 0x3300, 'end': 0x33FF }, { 'begin': 0xAC00, 'end': 0xD7AF }, { 'begin': 0xD800, 'end': 0xDFFF }, { 'begin': 0x10900, 'end': 0x1091F }, { 'begin': 0x4E00, 'end': 0x9FFF }, { 'begin': 0xE000, 'end': 0xF8FF }, { 'begin': 0x31C0, 'end': 0x31EF }, { 'begin': 0xFB00, 'end': 0xFB4F }, { 'begin': 0xFB50, 'end': 0xFDFF }, { 'begin': 0xFE20, 'end': 0xFE2F }, { 'begin': 0xFE10, 'end': 0xFE1F }, { 'begin': 0xFE50, 'end': 0xFE6F }, { 'begin': 0xFE70, 'end': 0xFEFF }, { 'begin': 0xFF00, 'end': 0xFFEF }, { 'begin': 0xFFF0, 'end': 0xFFFF }, { 'begin': 0x0F00, 'end': 0x0FFF }, { 'begin': 0x0700, 'end': 0x074F }, { 'begin': 0x0780, 'end': 0x07BF }, { 'begin': 0x0D80, 'end': 0x0DFF }, { 'begin': 0x1000, 'end': 0x109F }, { 'begin': 0x1200, 'end': 0x137F }, { 'begin': 0x13A0, 'end': 0x13FF }, { 'begin': 0x1400, 'end': 0x167F }, { 'begin': 0x1680, 'end': 0x169F }, { 'begin': 0x16A0, 'end': 0x16FF }, { 'begin': 0x1780, 'end': 0x17FF }, { 'begin': 0x1800, 'end': 0x18AF }, { 'begin': 0x2800, 'end': 0x28FF }, { 'begin': 0xA000, 'end': 0xA48F }, { 'begin': 0x1700, 'end': 0x171F }, { 'begin': 0x10300, 'end': 0x1032F }, { 'begin': 0x10330, 'end': 0x1034F }, { 'begin': 0x10400, 'end': 0x1044F }, { 'begin': 0x1D000, 'end': 0x1D0FF }, { 'begin': 0x1D400, 'end': 0x1D7FF }, { 'begin': 0xFF000, 'end': 0xFFFFD }, { 'begin': 0xFE00, 'end': 0xFE0F }, { 'begin': 0xE0000, 'end': 0xE007F }, { 'begin': 0x1900, 'end': 0x194F }, { 'begin': 0x1950, 'end': 0x197F }, { 'begin': 0x1980, 'end': 0x19DF }, { 'begin': 0x1A00, 'end': 0x1A1F }, { 'begin': 0x2C00, 'end': 0x2C5F }, { 'begin': 0x2D30, 'end': 0x2D7F }, { 'begin': 0x4DC0, 'end': 0x4DFF }, { 'begin': 0xA800, 'end': 0xA82F }, { 'begin': 0x10000, 'end': 0x1007F }, { 'begin': 0x10140, 'end': 0x1018F }, { 'begin': 0x10380, 'end': 0x1039F }, { 'begin': 0x103A0, 'end': 0x103DF }, { 'begin': 0x10450, 'end': 0x1047F }, { 'begin': 0x10480, 'end': 0x104AF }, { 'begin': 0x10800, 'end': 0x1083F }, { 'begin': 0x10A00, 'end': 0x10A5F }, { 'begin': 0x1D300, 'end': 0x1D35F }, { 'begin': 0x12000, 'end': 0x123FF }, { 'begin': 0x1D360, 'end': 0x1D37F }, { 'begin': 0x1B80, 'end': 0x1BBF }, { 'begin': 0x1C00, 'end': 0x1C4F }, { 'begin': 0x1C50, 'end': 0x1C7F }, { 'begin': 0xA880, 'end': 0xA8DF }, { 'begin': 0xA900, 'end': 0xA92F }, { 'begin': 0xA930, 'end': 0xA95F }, { 'begin': 0xAA00, 'end': 0xAA5F }, { 'begin': 0x10190, 'end': 0x101CF }, { 'begin': 0x101D0, 'end': 0x101FF }, { 'begin': 0x102A0, 'end': 0x102DF }, { 'begin': 0x1F030, 'end': 0x1F09F }
];

let MacStandardGlyphOrdering: string[] = [
  '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', 'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex', 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat'
]

let getUnicodeRangeFor = (value: number): number => {
  for (let i = 0; i < UnicodeRanges.length; i++) {
    let range: Record<string, number> = UnicodeRanges[i];
    if (value >= range.begin && value < range.end) {
      return i;
    }
  }
  return -1;
}

let isRTLRangeFor = (value: number): boolean => {
  let range:Record<string, number> = UnicodeRanges[Number_13];
  if (value >= range.begin && value < range.end) {
    return true;
  }

  range = UnicodeRanges[Number_11];
  if (value >= range.begin && value < range.end){
    return true;
  }

  return false;
}

let isSpecialUnicode = (unicode: number): boolean => {
  return (unicode <= Number_0x1F || (unicode >= 127 && unicode < kSizeOfGlyphArea)) ||
    (unicode >= kCmapGlyphOffset &&
      unicode < kCmapGlyphOffset + kSizeOfGlyphArea);
}

class OTF {
  file: string = "";
  virtualOffset?: number = Number_9 * (Number_3 * Number_4);

  constructor(file: string = '', virtualOffset: number = Number_9 * (Number_4 * Number_4) ) {
    this.file = file;
    this.virtualOffset = virtualOffset;
  }
}

class RangesPdf {
  start: number;
  end: number;
  codeIndices: number[];
  constructor(start: number, end: number, codeIndices: number[]) {
    this.start = start;
    this.end = end;
    this.codeIndices = codeIndices;
  }
}

class CidSystemInfoStruct {
  registry?: string | null;
  ordering?: string;
  supplement?: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
  constructor(registry?: string|null , ordering?: string , supplement?: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name ) {
    this.registry = registry
    this.ordering = ordering
    this.supplement = supplement
  }
}

class FontProperties {
  type: string = '';
  subtype?: string = '';
  file?: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = null;
  length1?: number = 0;
  length2?: number = 0;
  composite?: boolean = false ;
  wideChars?: boolean = false;
  fixedPitch?: boolean = false;
  fontMatrix?: number[] | null = null;
  firstChar: number = -1;
  lastChar: number = -1;
  bbox?: number[] | null = null;
  ascent?: number | null = null;
  descent?: number | null = null;
  xHeight?: number | null = null;
  capHeight?: number | null = null;
  flags?: number | null = null;
  italicAngle?: number | null = null;
  coded?: boolean = false;
  loadedName?: string | null = null;

  baseEncoding?: string[] | null = null;
  differences?: string[] | null = null;
  toUnicode?: number[] | null = null;
  cidSystemInfo?: CidSystemInfoStruct | null = null;
  ignore?: boolean | null = null;

  cidToGidMap?: number[] = new Array<number>();
  hasEncoding?: boolean = false;

  defaultWidth?: number = 0;
  widths?: Map<string, number> | number[] | null = null;

  charProcOperatorList?: Map<string, OperatorList> | null = null;
  glyphNames?: (string | null)[] | null = null;
  glyphNameMap?: Map<string, number | null> | null = null;

  privateData?: Map<string, object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name> | null = null;

}

class GlyphsStruct {

  unicode?: number = 0  ;
  code?: number = 0;
  name?: string = '';
  glyph?: string | null = null;
  fontChar?: string | null = null;
  width?: number = 0;
  disabled?: boolean = false;
  operatorList?: OperatorList | null = null;
  data?: (string | number)[] | null = null;
  lsb?: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | null = null;
  gid?: number = 0;
  charstring?: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] | null = null;
}

class TableEntry {
  tag: string = '';
  checksum?: number = 0;
  length?: number = 0;
  offset?: number = 0;
  data?: Uint8Array | null = null;
}

class OpenTypeHeader {
  version: string;
  numTables: number;
  searchRange: number;
  entrySelector: number;
  rangeShift: number;

  constructor(version: string, numTables: number, searchRange: number, entrySelector: number, rangeShift: number) {
    this.version = version;
    this.numTables = numTables;
    this.searchRange = searchRange;
    this.entrySelector = entrySelector;
    this.rangeShift = rangeShift;
  }
}

class CMapTableStruct {
  glyphs: GlyphsStruct[];
  ids: number[];
  hasShortCmap: boolean = false;

  constructor(glyphs: GlyphsStruct[], ids: number[], hasShortCmap: boolean = false) {
    this.glyphs = glyphs;
    this.ids = ids;
    this.hasShortCmap = hasShortCmap;
  }
}

class CMapRecordStruct {
  platformID: number;
  encodingID: number;
  offset: number;
  constructor(platformID: number, encodingID: number, offset: number) {
    this.platformID = platformID;
    this.encodingID = encodingID;
    this.offset = offset;
  }
}

class CMapSegmentStruct {
  start?: number = 0;
  end?: number;
  delta?: number = 0;
  offsetIndex?: number = 0;

  constructor(start: number = 0, end: number, delta: number = 0, offsetIndex: number = 0) {
    this.start = start;
    this.end = end;
    this.delta = delta;
    this.offsetIndex = offsetIndex;
  }

}


class FontInspector {
  static enabled: boolean = false;

  static fontAdded(font: Font, url: string) {

  }
}

class Font {
  name: string ;
  coded: boolean;
  charProcOperatorList: Map<string,OperatorList>;
  sizes: number[];
  isSerifFont: boolean;
  isSymbolicFont: boolean;
  type: string;
  loadedName: string;
  loading: boolean = false;
  differences: string[] = new Array<string>();
  widths: number[]|Map<string,number> | null = null;
  defaultWidth: number = 0;
  composite: boolean = false;
  wideChars: boolean = false;
  hasEncoding: boolean = false;
  fontMatrix: number[] | null = null;
  widthMultiplier: number = 0;
  encoding: string[] = new Array<string>();
  toUnicode: number[] | null = null;
  toFontChar: number[] = new Array<number>();
  attached:boolean | null = null;
  data: Uint8Array | null = null;
  bold: boolean = false;
  italic: boolean = false;
  black: boolean = false;
  noUnicodeAdaptation: boolean = false;
  useToFontChar: boolean = false;
  glyphNameMap: Map<string,number> = new Map();
  unicodeIsEnabled: boolean[] | null = null;
  cidToUnicode: number[] = new Array<number>();
  unicodeToCID: number[] = new Array<number>();
  charsCache: Map<string,GlyphsStruct[]> | null;
  mimetype: string | null;
  numFonts: number = 0;

  constructor(name: string, file: Stream, properties: FontProperties) {
    this.name = name;
    this.coded = properties.coded;
    this.charProcOperatorList = properties.charProcOperatorList;
    this.sizes = new Array<number>();

    let names: string[] | string = name.split('+');
    names = names.length > 1 ? names[1] : names[0];
    names = names.split(new RegExp('[-,_]','g'))[0];

    this.isSerifFont = !!(properties.flags & FontFlags.Serif);
    this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);

    let type:string = properties.type;
    this.type = type;

    if (properties.ignore) {
      this.loadedName = this.isSerifFont ? 'serif' : 'sans-serif';
      this.loading = false;
      return;
    }

    this.differences = properties.differences;
    this.widths = properties.widths;
    this.defaultWidth = properties.defaultWidth;
    this.composite = properties.composite;
    this.wideChars = properties.wideChars;
    this.hasEncoding = properties.hasEncoding;

    this.fontMatrix = properties.fontMatrix;
    this.widthMultiplier = 1.0;

    if (type === 'Type3') {
      this.encoding = properties.baseEncoding;
      return;
    }

    this.loadCidToUnicode(properties);
    if (properties.toUnicode !== null && properties.toUnicode !== undefined){
      this.toUnicode = properties.toUnicode;
    } else {
      this.rebuildToUnicode(properties);
    }

    this.toFontChar = this.buildToFontChar(this.toUnicode);
    if (file === null || file === undefined){
      let fontName:string = name.replace(new RegExp('[,_]','g'), '-');
      fontName = stdFontMap.get(fontName) || nonStdFontMap.get(fontName) || fontName;

      this.bold = (fontName.toLowerCase().indexOf('bold') !== -1);
      this.italic = (fontName.toLowerCase().indexOf('oblique') !== -1) || (fontName.toLowerCase().indexOf('italic') !== -1);

      this.black = (name.indexOf('Black') !== -1);

      this.encoding = properties.baseEncoding;
      this.noUnicodeAdaptation = true;
      this.loadedName = fontName.split('-')[0];
      this.loading = false;
      return;
    }

    let data: Uint8Array = new Uint8Array();
    switch (type) {
      case 'Type1':
      case 'CIDFontType0':
        this.mimetype = 'font/opentype';

        let subtype: string = properties.subtype;
        let cff:CFFFont | Type1Font = (subtype == 'Type1C' || subtype == 'CIDFontType0C') ?
          new CFFFont(file, properties) : new Type1Font(name, file, properties);

        data = this.convert(name, cff, properties);
        break;

      case 'TrueType':
      case 'CIDFontType2':
        this.mimetype = 'font/opentype';



        data = this.checkAndRepair(name, file, properties);
        break;

      default:
        warn(`Font ${properties.type} is not supported`);
        break;
    }

    this.data = data;
    this.fontMatrix = properties.fontMatrix;
    this.widthMultiplier = properties.fontMatrix == null ? 1.0 : 1.0 / properties.fontMatrix![0]
    this.encoding = properties.baseEncoding;
    this.loadedName = properties.loadedName;
    this.loading = true;
  }


  getUniqueName(): string {
    let result:string = 'pdfFont'+ this.numFonts;
    this.numFonts += 1;
    return result;
  }

  stringToArray(str: string): Uint8Array {
    let array: Uint8Array = new Uint8Array(str.length);
    for (let i = 0; i < str.length; ++i) {
      array[i] = str.charCodeAt(i);
    }
    return array;
  }

  arrayToString(arr: Uint8Array): string {
    let str:string = ''
    for (let i = 0; i < arr.length; ++i) {
      str += String.fromCharCode(arr[i]);
    }
    return str;
  }

  int16(bytes: number[]): number {
    return (bytes[0] << Number_8) + (bytes[1] & Number_0xff);
  }

  int32(bytes: number[]): number {
    return (bytes[0] << Number_24) + (bytes[1] << Number_16) +
      (bytes[Number_2] << Number_8) + (bytes[Number_3] & Number_0xff);
  }

  getMaxPower2(number: number): number {
    let maxPower:number = 0;
    let value:number = number;
    while (value >= Number_2) {
      value /= Number_2;
      maxPower += 1;
    }

    value = Number_2;
    if (maxPower > 1) {
      for (let i = 1; i < maxPower; i++) {
        value *= Number_2;
      }
    }
    return value;
  }

  string16(value: number): string {
    return String.fromCharCode((value >> Number_8) & Number_0xff) +
    String.fromCharCode(value & Number_0xff);
  }

  safeString16(value: number): string {
    let clampedValue:number = value > Number_0x7FFF ? Number_0x7FFF : value < -Number_0x8000 ? -Number_0x8000 : value;
    return String.fromCharCode((clampedValue >> Number_8) & Number_0xff) +
    String.fromCharCode(clampedValue & Number_0xff);
  }

  string32(value: number): string {
    let string24: string = String.fromCharCode((value >> Number_24) & Number_0xff);
    let string16: string = String.fromCharCode((value >> Number_16) & Number_0xff);
    let string8: string = String.fromCharCode((value >> Number_8) & Number_0xff);
    let string: string = String.fromCharCode(value & Number_0xff);

    return  string24 + string16 + string8 + string;
  }

  createOpenTypeHeader(sfnt: string, file: OTF, numTables: number): void {
    let modifiedSfnt: string = sfnt
    if (modifiedSfnt === 'true') {
      modifiedSfnt = this.string32(0x00010000);
    }

    let header: string = modifiedSfnt;

    header += this.string16(numTables);

    let tablesMaxPower2:number = this.getMaxPower2(numTables);
    let searchRange:number = tablesMaxPower2 * Number_16;
    header += this.string16(searchRange);
    header += this.string16(Math.log(tablesMaxPower2) / Math.log(Number_2));
    header += this.string16(numTables * 16 - searchRange);

    file.file += header;
    file.virtualOffset += header.length;
  }

  createTableEntry(file: OTF, tag: string, data: Uint8Array): void {
    let offset: number = file.virtualOffset;
    let length: number = data.length;

    while ((data.length & Number_3) !== 0){
      data = new Uint8Array([...data,0x00]);
    }

    while ((file.virtualOffset & Number_3) !== 0) {
      file.virtualOffset += 1;
    }

    let checksum: number = 0;
    let n: number = data.length;
    for (let i = 0; i < n; i += Number_4) {
      let chunk: number[] = [data[i],data[i+1],data[i+ Number_2],data[i+ Number_3]]
      checksum = (checksum + this.int32(chunk)) | 0;
    }

    let tableEntry:string = (tag + this.string32(checksum) + this.string32(offset) + this.string32(length));
    file.file += tableEntry;
    file.virtualOffset += data.length;
  }

  getRanges(glyphs:GlyphsStruct[]): RangesPdf[] {


    let codes: GlyphsStruct[] = new Array<GlyphsStruct>();
    let length: number = glyphs.length;
    for (let n = 0; n < length; ++n) {
      let glyphsStruct: GlyphsStruct = new GlyphsStruct();
      glyphsStruct.unicode = glyphs[n].unicode;
      glyphsStruct.code = n;
      codes.push(glyphsStruct);
    }
    codes.sort((a, b) => a.unicode - b.unicode);

    let ranges:RangesPdf[] = new Array<RangesPdf>();
    let n:number = 0;
    while (n < length) {
      let start: number = codes[n].unicode;
      let codeIndices: number[] = [codes[n].code];
      n += 1;
      let end:number = start;
      while (n < length && end + 1 == codes[n].unicode) {
        codeIndices.push(codes[n].code);
        end += 1;
        n += 1;
      }
      ranges.push({start, end, codeIndices});
    }

    return ranges;
  }

  createCMapTable(glyphs:GlyphsStruct[], deltas: number[]): Uint8Array {
    let ranges: RangesPdf[] = this.getRanges(glyphs);

    let numTables: number = 1;
    let cmap: string = '\x00\x00' +
    this.string16(numTables) +
      '\x00\x03' +
      '\x00\x01' +
    this.string32(Number_4 + numTables * Number_8);

    let segCount: number = ranges.length + 1;
    let segCount2: number = segCount * Number_2;
    let searchRange: number = this.getMaxPower2(segCount) * Number_2;
    let searchEntry: number = Math.log(segCount) / Math.log(Number_2);
    let rangeShift: number = Number_2 * segCount - searchRange;

    let startCount: string = '';
    let endCount: string = '';
    let idDeltas: string = '';
    let idRangeOffsets: string = '';
    let glyphsIds: string = '';
    let bias: number = 0;
    if (deltas !== null && deltas !== undefined){
      for (let i = 0; i < segCount - 1; i++) {
        let range: RangesPdf = ranges[i];
        let start:number = range.start;
        let end:number = range.end;
        let offset:number = (segCount - i) * Number_2 + bias * Number_2;
        bias += (end - start + 1);

        startCount += this.string16(start);
        endCount += this.string16(end);
        idDeltas += this.string16(0);
        idRangeOffsets += this.string16(offset);

        let codes: number[] = range.codeIndices;
        for (let j = 0; j < codes.length; ++j) {
          glyphsIds += this.string16(deltas[codes[j]]);
        }
      }
    } else {
      for (let i = 0; i < segCount - 1; i++) {
        let range: RangesPdf = ranges[i];
        let start: number = range.start;
        let end: number = range.end;
        let startCode: number = range.codeIndices[0];

        startCount += this.string16(start);
        endCount += this.string16(end);
        idDeltas += this.string16((startCode - start + 1) & Number_0xFFFF);
        idRangeOffsets += this.string16(0);
      }
    }

    endCount += '\xFF\xFF';
    startCount += '\xFF\xFF';
    idDeltas += '\x00\x01';
    idRangeOffsets += '\x00\x00';

    let format314: string = '\x00\x00' +
    this.string16(segCount2) +
    this.string16(searchRange) +
    this.string16(searchEntry) +
    this.string16(rangeShift) +
      endCount + '\x00\x00' + startCount +
      idDeltas + idRangeOffsets + glyphsIds;

    return this.stringToArray(cmap +
      '\x00\x04' +
    this.string16(format314.length + Number_4) +
      format314);
  }

  createOS2Table(properties: FontProperties, charstrings: GlyphsStruct[], pdfOverride?:Map<string, number>): string {

    pdfOverride = pdfOverride ?? new Map<string, number>([
      ["unitsPerEm", 0], ["yMax", 0], ["yMin", 0], ["ascent", 0], ["descent", 0]
    ]);

    let ulUnicodeRange1: number = 0;
    let ulUnicodeRange2: number = 0;
    let ulUnicodeRange3: number = 0;
    let ulUnicodeRange4: number = 0;

    let firstCharIndex: number | null = null;
    let lastCharIndex: number = 0;
    if (charstrings !== null && charstrings !== undefined){
      for (let i = 0; i < charstrings.length; ++i) {
        let code: number = charstrings[i].unicode;
        if (firstCharIndex > code || !firstCharIndex) {
          firstCharIndex = code;
        }
        if (lastCharIndex < code) {
          lastCharIndex = code;
        }

        let position: number = getUnicodeRangeFor(code);
        if (position < Number_32) {
          ulUnicodeRange1 |= 1 << position;
        } else if (position < Number_64) {
          ulUnicodeRange2 |= 1 << position - Number_32;
        } else if (position < Number_96) {
          ulUnicodeRange3 |= 1 << position - Number_64;
        } else if (position < Number_123) {
          ulUnicodeRange4 |= 1 << position - Number_96;
        } else {
          error('Unicode ranges Bits > 123 are reserved for internal usage');
        }
      }
    } else {

      firstCharIndex = 0;
      lastCharIndex = Number_255;
    }

    let unitsPerEm: number = pdfOverride.get("unitsPerEm") ?? kPDFGlyphSpaceUnits;
    let typoAscent: number = pdfOverride.get("ascent") ?? properties.ascent;
    let typoDescent: number = pdfOverride.get("descent") ?? properties.descent;
    let winAscent: number = pdfOverride.get("yMax") ?? typoAscent;
    let winDescent: number = -pdfOverride.get("yMin") ?? -typoDescent;

    if (unitsPerEm !== kPDFGlyphSpaceUnits && undefined ==  pdfOverride.get("ascent")) {
      typoAscent = Math.round(typoAscent * unitsPerEm / kPDFGlyphSpaceUnits);
      typoDescent = Math.round(typoDescent * unitsPerEm / kPDFGlyphSpaceUnits);
      winAscent = typoAscent;
      winDescent = -typoDescent;
    }
    return '\x00\x03' +
      '\x02\x24' +
      '\x01\xF4' +
      '\x00\x05' +
      '\x00\x00' +
      '\x02\x8A' +
      '\x02\xBB' +
      '\x00\x00' +
      '\x00\x8C' +
      '\x02\x8A' +
      '\x02\xBB' +
      '\x00\x00' +
      '\x01\xDF' +
      '\x00\x31' +
      '\x01\x02' +
      '\x00\x00' +
      '\x00\x00\x06' +
    String.fromCharCode(properties.fixedPitch ? Number_0x09 : Number_0x00) +
      '\x00\x00\x00\x00\x00\x00' +
    this.string32(ulUnicodeRange1) +
    this.string32(ulUnicodeRange2) +
    this.string32(ulUnicodeRange3) +
    this.string32(ulUnicodeRange4) +
      '\x2A\x32\x31\x2A' +
    this.string16(properties.italicAngle ? 1 : 0) +
    this.string16(firstCharIndex ||
    properties.firstChar) +
    this.string16(lastCharIndex || properties.lastChar) +
    this.string16(typoAscent) +
    this.string16(typoDescent) +
      '\x00\x64' +
    this.string16(winAscent) +
    this.string16(winDescent) +
      '\x00\x00\x00\x00' +
      '\x00\x00\x00\x00' +
    this.string16(properties.xHeight) +
    this.string16(properties.capHeight) +
    this.string16(0) +
    this.string16(firstCharIndex || properties.firstChar) +
      '\x00\x03';
  }

  createPostTable(properties: FontProperties): string {
    let angle: number = Math.floor(properties.italicAngle * (Math.pow(Number_2, Number_16)));
    return '\x00\x03\x00\x00' +
    this.string32(angle) +
      '\x00\x00' +
      '\x00\x00' +
    this.string32(Number(properties.fixedPitch)) +
      '\x00\x00\x00\x00' +
      '\x00\x00\x00\x00' +
      '\x00\x00\x00\x00' +
      '\x00\x00\x00\x00';
  }


  createNameTable(name: string): string {
    let strings: string[] = [
      'Original licence', name, 'Unknown', 'uniqueID', name, 'Version 0.11', '', 'Unknown', 'Unknown', 'Unknown'
    ];

    let stringsUnicode: string[] = new Array<string>();
    for (let i = 0; i < strings.length; i++) {
      let str: string = strings[i];
      let strUnicode: string = '';
      for (let j = 0; j < str.length; j++) {
        strUnicode += this.string16(str.charCodeAt(j));
      }
      stringsUnicode.push(strUnicode);
    }

    let names: string[][] = [strings, stringsUnicode];
    let platforms: string[] = ['\x00\x01', '\x00\x03'];
    let encodings: string[] = ['\x00\x00', '\x00\x01'];
    let languages: string[] = ['\x00\x00', '\x04\x09'];

    let namesRecordCount: number = strings.length * platforms.length;
    let nameTable: string =
      '\x00\x00' +
      this.string16(namesRecordCount) +
      this.string16(namesRecordCount * Number_12 + Number_6);

    let strOffset: number = 0;
    for (let i = 0; i < platforms.length; i++) {
      let strs: string[] = names[i];
      for (let j = 0; j < strs.length; j++) {
        let str: string = strs[j];
        let nameRecord: string =
          platforms[i] +
          encodings[i] +
          languages[i] +
          this.string16(j) +
          this.string16(str.length) +
          this.string16(strOffset);
        nameTable += nameRecord;
        strOffset += str.length;
      }
    }

    nameTable += strings.join('') + stringsUnicode.join('');
    return nameTable;
  }


  checkAndRepair(name: string, font: Stream, properties: FontProperties): Uint8Array {

    let readTableEntry = (file: Stream): TableEntry => {

      let tagArray: Uint8Array = file.getBytes(Number_4);
      let tag = String.fromCharCode(tagArray[0]) +
      String.fromCharCode(tagArray[1]) +
      String.fromCharCode(tagArray[Number_2]) +
      String.fromCharCode(tagArray[Number_3]);

      let checksum: number = this.int32(Array.from(file.getBytes(Number_4)));
      let offset: number = this.int32(Array.from(file.getBytes(Number_4)));
      let length: number = this.int32(Array.from(file.getBytes(Number_4)));


      let previousPosition: number = file.pos;
      file.pos = file.start;
      file.skip(offset);
      let data: Uint8Array = file.getBytes(length);
      file.pos = previousPosition;

      if (tag === 'head') {

        data[Number_8] = 0;
        data[Number_9] = 0;
        data[Number_10] = 0;
        data[Number_11] = 0;
        data[Number_17] |= Number_0x20;
      }

      let tableEntry: TableEntry = new TableEntry()
      tableEntry.tag = tag;
      tableEntry.checksum = checksum;
      tableEntry.length = length;
      tableEntry.offset = offset;
      tableEntry.data = data;
      return tableEntry
    };

    let readOpenTypeHeader = (ttf: Stream): OpenTypeHeader => {

      return new OpenTypeHeader(
        this.arrayToString(ttf.getBytes(Number_4)),
        this.int16(Array.from(ttf.getBytes(Number_2))),
        this.int16(Array.from(ttf.getBytes(Number_2))),
        this.int16(Array.from(ttf.getBytes(Number_2))),
        this.int16(Array.from(ttf.getBytes(Number_2)))
      );
    };

    let createGlyphNameMap = (glyphs: GlyphsStruct[], ids: number[], properties: FontProperties): void => {
      let glyphNames: string[] = properties.glyphNames;
      if (glyphNames === null || glyphNames === undefined){
        properties.glyphNameMap = new Map();
        return;
      }
      let glyphNameMap: Map<string, number | null> = new Map();
      let encoding: string[] = new Array<string>();

      for (let i = 0; i < glyphs.length; ++i) {
        let glyphName: string = glyphNames[ids[i]];
        if (glyphName === null || glyphName === undefined){
          continue;
        }

        let unicode: number = glyphs[i].unicode;
        glyphNameMap.set(glyphName, unicode);
        let code: number = glyphs[i].code;
        encoding[code] = glyphName;
      }

      properties.glyphNameMap = glyphNameMap;

      if (!properties.hasEncoding)
        properties.baseEncoding = encoding;
    };




    let readCMapTable = (cmap: TableEntry, font: Stream): CMapTableStruct => {
      let start: number = (font.start ? font.start : 0) + cmap.offset;
      font.pos = start;

      let version: number = this.int16(Array.from(font.getBytes(Number_2)));
      let numRecords: number = this.int16(Array.from(font.getBytes(Number_2)));

      let records: CMapRecordStruct[] = new Array<CMapRecordStruct>();
      for (let i = 0; i < numRecords; i++) {
        records.push(new CMapRecordStruct(
          this.int16(Array.from(font.getBytes(Number_2))),
          this.int16(Array.from(font.getBytes(Number_2))),
          this.int32(Array.from(font.getBytes(Number_4)))));
      }


      records.sort((a, b): number =>{
        return ((a.platformID << Number_16) + a.encodingID) - ((b.platformID << Number_16) + b.encodingID);
      });

      let tables: CMapRecordStruct[] = new Array(records[0]);
      for (let i = 1; i < numRecords; i++) {
        let current: CMapRecordStruct = records[i];
        let previous: CMapRecordStruct = records[i - 1];
        if (((current.platformID << Number_16) + current.encodingID) <=
          ((previous.platformID << Number_16) + previous.encodingID))
          continue;
        tables.push(current);
      }

      let missing: number = numRecords - tables.length;
      if (missing > 0) {
        numRecords = tables.length;
        let data: string = this.string16(version) + this.string16(numRecords);

        for (let i = 0; i < numRecords; i++) {
          let table: CMapRecordStruct = tables[i];
          data += this.string16(table.platformID) +
          this.string16(table.encodingID) +
          this.string32(table.offset);
        }

        for (let i = 0; i < data.length; i++) {
          cmap.data[i] = data.charCodeAt(i);
        }
      }

      for (let i = 0; i < numRecords; i++) {
        let table: CMapRecordStruct = tables[i];
        font.pos = start + table.offset;

        let format: number = this.int16(Array.from(font.getBytes(Number_2)));
        let length: number = this.int16(Array.from(font.getBytes(Number_2)));
        let language: number = this.int16(Array.from(font.getBytes(Number_2)));

        if (format == 0) {
          let glyphs: GlyphsStruct[] = new Array<GlyphsStruct>();
          let ids: number[] = new Array<number>();
          for (let j = 0; j < Number_256; j++) {
            let index: number | null = font.getByte();
            if (index === null || index === undefined){
              let glyphsStruct: GlyphsStruct = new GlyphsStruct()
              glyphsStruct.unicode = j
              glyphsStruct.code = j
              glyphs.push(glyphsStruct);
              ids.push(index);
            }
          }
          return new CMapTableStruct(
            glyphs,
            ids,
            true
          );
        } else if (format == Number_4) {
          let segCount: number = (this.int16(Array.from(font.getBytes(Number_2))) >> 1);
          font.getBytes(Number_6);
          let segments: CMapSegmentStruct[] = new Array<CMapSegmentStruct>();
          for (let segIndex = 0; segIndex < segCount; segIndex++) {
            segments.push({ end: this.int16(Array.from(font.getBytes(Number_2))) });
          }
          font.getBytes(Number_2);
          for (let segIndex = 0; segIndex < segCount; segIndex++) {
            segments[segIndex].start = this.int16(Array.from(font.getBytes(Number_2)));
          }

          for (let segIndex = 0; segIndex < segCount; segIndex++) {
            segments[segIndex].delta = this.int16(Array.from(font.getBytes(Number_2)));
          }

          let offsetsCount: number = 0;
          for (let segIndex = 0; segIndex < segCount; segIndex++) {
            let segment: CMapSegmentStruct = segments[segIndex];
            let rangeOffset: number = this.int16(Array.from(font.getBytes(Number_2)));
            if (rangeOffset == 0) {
              segment.offsetIndex = -1;
              continue;
            }

            let offsetIndex: number = (rangeOffset >> 1) - (segCount - segIndex);
            segment.offsetIndex = offsetIndex;
            offsetsCount = Math.max(offsetsCount, offsetIndex +
            segment.end - segment.start + 1);
          }

          let offsets: number[] = new Array<number>();
          for (let j = 0; j < offsetsCount; j++) {
            offsets.push(this.int16(Array.from(font.getBytes(Number_2))));
          }

          let glyphs: GlyphsStruct[] = new Array<GlyphsStruct>();
          let ids: number[] = new Array<number>();

          for (let segIndex = 0; segIndex < segCount; segIndex++) {
            let segment: CMapSegmentStruct = segments[segIndex];
            let start: number = segment.start;
            let end: number = segment.end;
            let delta: number = segment.delta;
            let offsetIndex: number = segment.offsetIndex;

            for (let j = start; j <= end; j++) {
              if (j == Number_0xFFFF) {
                continue;
              }

              let glyphCode: number = offsetIndex < 0 ? j :
                offsets[offsetIndex + j - start];
              glyphCode = (glyphCode + delta) & Number_0xFFFF;
              if (glyphCode == 0) {
                continue;
              }
              let glyphsStruct: GlyphsStruct = new GlyphsStruct();
              glyphsStruct.unicode = j;
              glyphsStruct.code = j;
              glyphs.push(glyphsStruct);
              ids.push(glyphCode);
            }
          }

          return new CMapTableStruct(
            glyphs,
            ids
          );
        } else if (format == Number_6) {

          let firstCode: number = this.int16(Array.from(font.getBytes(Number_2)));
          let entryCount: number = this.int16(Array.from(font.getBytes(Number_2)));

          let glyphs: GlyphsStruct[] = new Array<GlyphsStruct>();
          let ids: number[] = new Array<number>();
          for (let j = 0; j < entryCount; j++) {
            let glyphCode: number = this.int16(Array.from(font.getBytes(Number_2)));
            let code: number = firstCode + j;
            let glyphsStruct: GlyphsStruct = new GlyphsStruct();
            glyphsStruct.unicode = code;
            glyphsStruct.code = code;
            glyphs.push(glyphsStruct);
            ids.push(glyphCode);
          }

          return new CMapTableStruct(
            glyphs,
            ids
          );

        }

      }

      error('Unsupported cmap table format');

      return new CMapTableStruct(
        new Array<GlyphsStruct>(),
        new Array<number>(),
      );
    };

    let sanitizeMetrics = (font: Stream, header: TableEntry, metrics: TableEntry, numGlyphs: number):void => {
      if ((header === null || header === undefined) && (metrics === null || metrics === undefined)){
        return;
      }
      if ((header === null || header === undefined) && (metrics !== null || metrics !== undefined)){
        metrics.data = null;
        return;
      }

      font.pos = (font.start ? font.start : 0) + header.offset;
      font.pos += header.length - Number_2;
      let numOfMetrics: number = this.int16(Array.from(font.getBytes(Number_2)));

      let numOfSidebearings: number = numGlyphs - numOfMetrics;
      let numMissing: number = numOfSidebearings -
        ((hmtx.length - numOfMetrics * Number_4) >> 1);
      if (numMissing > 0) {
        font.pos = (font.start ? font.start : 0) + metrics.offset;
        let entries: string = '';
        for (let i = 0; i < hmtx.length; i++) {
          entries += String.fromCharCode(font.getByte());

        }
        for (let i = 0; i < numMissing; i++) {
          entries += '\x00\x00';
        }
        metrics.data = this.stringToArray(entries);
      }
    };

    let sanitizeGlyph = (source: Uint8Array, sourceStart: number, sourceEnd: number, dest: Uint8Array, destStart: number): number => {

      dest = dest;
      if (sourceEnd - sourceStart <= Number_12) {
        return 0;
      }
      let glyf: Uint8Array = source.subarray(sourceStart, sourceEnd);
      let contoursCount: number = (glyf[0] << Number_8) | glyf[1];
      if (contoursCount & Number_0x8000) {

        dest.set(glyf, destStart);
        return glyf.length;
      }

      let j: number = Number_10;
      let flagsCount: number = 0;
      for (let i = 0; i < contoursCount; i++) {
        let endPoint: number = (glyf[j] << Number_8) | glyf[j + 1];
        flagsCount = endPoint + 1;
        j += Number_2;
      }
      let instructionsLength: number = (glyf[j] << Number_8) | glyf[j + 1];
      j += Number_2 + instructionsLength;
      let coordinatesLength: number = 0;
      for (let i = 0; i < flagsCount; i++) {
        let flag: number = glyf[j++];
        if ((flag & Number_0xC0) !== 0) {
          return 0;
        }
        let xyLength: number = ((flag & Number_2) ? 1 : (flag & Number_16) ? 0 : Number_2) +
          ((flag & Number_4) ? 1 : (flag & Number_32) ? 0 : Number_2);
        coordinatesLength += xyLength;
        if ((flag & Number_8) !== 0) {
          let repeat: number = glyf[j+1];
          j += repeat;
          coordinatesLength += repeat * xyLength;
        }
        j += 1;
      }
      let glyphDataLength: number = j + coordinatesLength;
      if (glyphDataLength > glyf.length) {
        return 0;
      }
      if (glyf.length - glyphDataLength > Number_3) {
        glyphDataLength = (glyphDataLength + Number_3) & ~ Number_3;
        dest.set(glyf.subarray(0, glyphDataLength), destStart);
        return glyphDataLength;
      }

      dest.set(glyf, destStart);
      return glyf.length;
    };

    let sanitizeGlyphLocations = (loca: TableEntry, glyf: TableEntry, numGlyphs: number, isGlyphLocationsLong: boolean): void => {
      let itemSize: number;
      let itemDecode: (data: Uint8Array, offset: number) => number;
      let itemEncode: (data: Uint8Array, offset: number, value: number) => void;

      if (isGlyphLocationsLong) {
        itemSize = Number_4;
        itemDecode = (data: Uint8Array, offset: number): number =>{
          return (data[offset] << Number_24) | (data[offset + 1] << Number_16) | (data[offset + Number_2] << Number_8) | data[offset + Number_3];
        };
        itemEncode = (data: Uint8Array, offset: number, value: number): void => {
          data = data;
          data[offset] = (value >>> Number_24) & Number_0xFF;
          data[offset + 1] = (value >> Number_16) & Number_0xFF;
          data[offset + Number_2] = (value >> Number_8) & Number_0xFF;
          data[offset + Number_3] = value & Number_0xFF;
        };
      } else {
        itemSize = Number_2;
        itemDecode = (data: Uint8Array, offset: number): number =>{
          return (data[offset] << Number_9) | (data[offset + 1] << 1);
        };
        itemEncode = (data: Uint8Array, offset: number, value: number): void => {
          data[offset] = (value >> Number_9) & Number_0xFF;
          data[offset + 1] = (value >> 1) & Number_0xFF;
        };
      }

      let locaData: Uint8Array = loca.data;
      let oldGlyfData: Uint8Array = glyf.data;
      let oldGlyfDataLength: number = oldGlyfData.length;
      let newGlyfData: Uint8Array = new Uint8Array(oldGlyfDataLength);
      let startOffset: number = itemDecode(locaData, 0);
      let writeOffset: number = 0;
      itemEncode(locaData, 0, writeOffset);

      for (let i = 0; i < numGlyphs; i++) {
        let j:number = i * itemSize;
        let endOffset: number = itemDecode(locaData, j);
        if (endOffset > oldGlyfDataLength) {

          itemEncode(locaData, j, writeOffset);
          startOffset = endOffset;
          continue;
        }

        let newLength: number = sanitizeGlyph(oldGlyfData, startOffset, endOffset,
          newGlyfData, writeOffset);
        writeOffset += newLength;
        itemEncode(locaData, j, writeOffset);
        startOffset = endOffset;
      }

      if (writeOffset == 0) {
        let simpleGlyph:Uint8Array = new Uint8Array(
          [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0]);
        for (let i = 0; i < numGlyphs; i++) {
          let j: number = (i + 1) * itemSize;
          itemEncode(locaData, j, simpleGlyph.length);
        }
        glyf.data = simpleGlyph;
        return;
      }

      glyf.data = newGlyfData.subarray(0, writeOffset);
    };

    let findEmptyGlyphs = (locaTable: TableEntry, isGlyphLocationsLong: boolean, emptyGlyphIds: boolean[]):void => {
      emptyGlyphIds = emptyGlyphIds
      let itemSize: number;
      let itemDecode: (data: Uint8Array, offset: number) => number;

      if (isGlyphLocationsLong) {
        itemSize = Number_4;
        itemDecode = (data: Uint8Array, offset: number): number => {
          return (data[offset] << Number_24) | (data[offset + 1] << Number_16) | (data[offset + Number_2] << Number_8) | data[offset + Number_3];
        };
      } else {
        itemSize = Number_2;
        itemDecode = (data: Uint8Array, offset: number): number => {
          return (data[offset] << Number_9) | (data[offset + 1] << 1);
        };
      }
      
      let data: Uint8Array = locaTable.data;
      let length: number = data.length;
      let lastOffset: number = itemDecode(data, 0);

      for (let i = itemSize; i < length; i += itemSize) {
        let j: number = i / itemSize
        let offset: number = itemDecode(data, i);
        if (offset == lastOffset) {
          emptyGlyphIds[j] = true;
        }
        lastOffset = offset;
      }
    };

    let readGlyphNameMap = (post: TableEntry, properties: FontProperties):void => {

      let start: number = (font.start ? font.start : 0) + post.offset;
      font.pos = start;

      let length: number = post.length;
      let end: number = start + length;
      let version: number = this.int32(Array.from(font.getBytes(Number_4)));
      font.getBytes(Number_28);

      let glyphNames: string[];

      switch (version) {
        case Number_0x00010000:
          glyphNames = MacStandardGlyphOrdering;
          break;
        case Number_0x00020000:
          let numGlyphs: number = this.int16(Array.from(font.getBytes(Number_2)));
          let glyphNameIndexes: number[] = new Array<number>();

          for (let i = 0; i < numGlyphs; ++i) {
            glyphNameIndexes.push(this.int16(Array.from(font.getBytes(Number_2))));
          }

          let customNames: string[] = new Array<string>();
          while (font.pos < end) {
            let stringLength: number = font.getByte();
            let string: string = '';

            for (let i = 0; i < stringLength; ++i) {
              string += font.getChar();
            }
            customNames.push(string);
          }
          glyphNames = new Array<string>();
          for (let i = 0; i < numGlyphs; ++i) {
            let j: number = glyphNameIndexes[i];
            if (j < 258) {
              glyphNames.push(MacStandardGlyphOrdering[j]);
              continue;
            }
            glyphNames.push(customNames[j - 258]);
          }
          break;
        case Number_0x00030000:
          break;
        default:
          warn(`Unknown/unsupported post table version ${version}`);
          break;
      }
      properties.glyphNames = glyphNames;
    };
    let isOS2Valid = (os2Table: TableEntry): boolean => {
      let data: Uint8Array = os2Table.data;
      let usWinAscent: number = (data[Number_74] << Number_8) | data[Number_75];
      return usWinAscent !== 0;
    };


    let requiredTables: string[] = ['OS/2', 'cmap', 'head', 'hhea', 'hmtx', 'maxp', 'name', 'post'];

    let header: OpenTypeHeader = readOpenTypeHeader(font);
    let numTables: number = header.numTables;

    let cmap: TableEntry;
    let post: TableEntry;
    let maxp: TableEntry;
    let hhea: TableEntry;
    let hmtx: TableEntry;
    let vhea: TableEntry;
    let vmtx: TableEntry;
    let head: TableEntry;
    let loca: TableEntry;
    let glyf: TableEntry;
    let os2: TableEntry;


    let tables: TableEntry[] = new Array<TableEntry>();

    for (let i = 0; i < numTables; i++) {
      let table: TableEntry = readTableEntry(font);
      let index: number = requiredTables.indexOf(table.tag);

      if (index !== -1) {
        if (table.tag == 'cmap') {
          cmap = table;
        }
        else if (table.tag == 'post') {
          post = table;
        }
        else if (table.tag == 'maxp') {
          maxp = table;
        }
        else if (table.tag == 'hhea') {
          hhea = table;
        }
        else if (table.tag == 'hmtx') {
          hmtx = table;
        }
        else if (table.tag == 'head') {
          head = table;
        }
        else if (table.tag == 'OS/2') {
          os2 = table;
        }
        requiredTables.splice(index, 1);
      } else {
        if (table.tag == 'vmtx') {
          vmtx = table;
        }
        else if (table.tag == 'vhea') {
          vhea = table;
        }
        else if (table.tag == 'loca') {
          loca = table;
        }
        else if (table.tag == 'glyf') {
          glyf = table;
        }
      }
      tables.push(table);

    }

    let newNumTables:number = tables.length + requiredTables.length;

    let ttf:OTF = new OTF();
    ttf.file = ""
    ttf.virtualOffset = newNumTables * (Number_4 * Number_4)

    this.createOpenTypeHeader(header.version, ttf, numTables);
    if ((os2 !== null && os2 !== undefined) && !isOS2Valid(os2)){
      let index:number = tables.indexOf(os2)
      if(index !== -1){
        tables.splice(index, 1);
      }
      os2 = null;
    }

    font.pos = (font.start || 0) + maxp.offset;
    let version: number = this.int16(Array.from(font.getBytes(Number_4)));
    let numGlyphs: number = this.int16(Array.from(font.getBytes(Number_2)));

    sanitizeMetrics(font, hhea, hmtx, numGlyphs);
    sanitizeMetrics(font, vhea, vmtx, numGlyphs);

    let isGlyphLocationsLong: number = this.int16([head.data[50], head.data[51]]);
    if ((head !== null && head !== undefined) && (loca !== null && loca !== undefined) && (glyf !== null && glyf !== undefined)) {
      sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong !== 0);
    }

    let emptyGlyphIds: boolean[] = new Array<boolean>();
    if (glyf !== null && glyf !== undefined){
      findEmptyGlyphs(loca, isGlyphLocationsLong !== 0, emptyGlyphIds);
    }

    if (hhea.data[Number_10] == 0 && hhea.data[Number_11] == 0) {
      hhea.data[Number_10] = Number_0xFF;
      hhea.data[Number_11] = Number_0xFF;
    }
    if (post !== null && post !== undefined){
      readGlyphNameMap(post, properties);
    }

    let glyphs: GlyphsStruct[];
    let ids: number[];

    if (properties.type == 'CIDFontType2') {
      if (cmap === null || cmap === undefined){
        cmap = new TableEntry()
        cmap.tag = 'cmap';
        cmap.data = null;
        tables.push(cmap);
      }

      let cidToGidMap: number[] = properties.cidToGidMap || [];
      let gidToCidMap: number[] = new Array<number>();
      gidToCidMap.push(0);

      if (cidToGidMap.length > 0) {
        for (let j = cidToGidMap.length - 1; j >= 0; j--) {
          let gid: number = cidToGidMap[j];
          if (gid !== 0 ) {
            gidToCidMap[gid] = j;
          }
        }

        let nextCid: number = cidToGidMap.length;
        for (let i = 1; i < numGlyphs; i++) {
          if (gidToCidMap[i] == 0) {
            gidToCidMap[i] = nextCid;
            nextCid += 1;
          }
        }
      }

      let usedUnicodes: boolean[] = new Array<boolean>();
      let unassignedUnicodeItems: number[] = new Array<number>();

      for (let i = 1; i < numGlyphs; i++) {
        let cid: number = Number(gidToCidMap[i]);

        let unicode: number|undefined  = this.toFontChar[cid];
        if (unicode !== undefined && typeof unicode === 'number' && !isSpecialUnicode(unicode) && !usedUnicodes.includes(unicode !== 0)) {

          usedUnicodes.splice(unicode, 0, true);
          let glyphsStruct: GlyphsStruct = new GlyphsStruct();
          glyphsStruct.unicode = unicode;
          glyphsStruct.code = cid;
          glyphs.push(glyphsStruct);
          ids.push(i);
        } else {
          unassignedUnicodeItems.push(i);
          continue;
        }
      }
      let unusedUnicode: number = kCmapGlyphOffset;

      for (let j = 0; j < unassignedUnicodeItems.length; j++) {
        let i: number = unassignedUnicodeItems[j];
        let cid: number = gidToCidMap[i] || i;

        while ( unusedUnicode < usedUnicodes.length) {
          unusedUnicode += 1;
        }

        if (unusedUnicode >= kCmapGlyphOffset + kSizeOfGlyphArea) {
          break;
        }
        unusedUnicode += 1;

        let unicode:number = unusedUnicode;
        this.toFontChar[cid] = unicode;
        usedUnicodes[unicode] = true;
        let glyphsStruct: GlyphsStruct = new GlyphsStruct();
        glyphsStruct.unicode = unicode;
        glyphsStruct.code = cid;
        glyphs.push(glyphsStruct)
        ids.push(i);
      }
    } else {
      let cmapTable: CMapTableStruct = readCMapTable(cmap, font);
      glyphs = cmapTable.glyphs;
      ids = cmapTable.ids;

      let hasShortCmap: boolean = !!cmapTable.hasShortCmap;
      let toFontChar: number[] = this.toFontChar;

      if (hasShortCmap && ids.length == numGlyphs) {
        for (let i = 0; i < ids.length; i++) {
          ids[i] = i;
        }
      }

      let unusedUnicode: number = kCmapGlyphOffset;
      let glyphNames: string[] = properties.glyphNames || new Array<string>();
      let encoding: string[] = properties.baseEncoding;
      let differences: string[] = properties.differences || new Array<string>();

      if (toFontChar !== null && toFontChar.length > 0) {
        let isIdentity: boolean = true;
        for (let i = 0; i < glyphs.length; i++) {
          if (glyphs[i].unicode !== i + 1) {
            isIdentity = false;
            break;
          }
        }


        if (isIdentity && !this.isSymbolicFont) {
          let usedUnicodes: number[] = new Array<number>();
          let unassignedUnicodeItems: number[] = new Array<number>();

          for (let i = 0; i < glyphs.length; i++) {
            let unicode: number = toFontChar[i + 1];
            if (unicode !== null && typeof unicode === 'number' && !usedUnicodes.includes(unicode)) {
              usedUnicodes.push(unusedUnicode);
              glyphs[i].unicode = unicode;
            } else {
              unassignedUnicodeItems.push(i);
            }
          }

          for (let j = 0; j < unassignedUnicodeItems.length; j++) {
            let i: number = unassignedUnicodeItems[j];

            while (usedUnicodes.includes(unusedUnicode)){
              unusedUnicode += 1;
            }

            let cid: number = i + 1;

            if (toFontChar[cid] === null) {
              toFontChar[cid] = unusedUnicode;
            }

            glyphs[i].unicode = unusedUnicode;
            usedUnicodes.push(unusedUnicode);
            unusedUnicode += 1;
          }

          this.useToFontChar = true;
        }
      }

      let glyphsRemoved: number = 0;

      for (let i = ids.length - 1; i >= 0; i--) {
        if (ids[i] < numGlyphs && (!emptyGlyphIds[ids[i]] || this.isSymbolicFont)) {
          continue;
        }

        ids.splice(i, 1);
        glyphs.splice(i, 1);
        glyphsRemoved += 1;
      }

      if (this.isSymbolicFont) {
        let minUnicode:number = Number_0xFFFF;
        let maxUnicode:number = 0;

        for (let i = 0; i < glyphs.length; i++) {
          let unicode: number = glyphs[i].unicode;
          minUnicode = Math.min(minUnicode, unicode);
          maxUnicode = Math.max(maxUnicode, unicode);
        }
        if ((maxUnicode & Number_0xFF00) !== (minUnicode & Number_0xFF00)) {
          this.isSymbolicFont = false;
        }
      }
      if (glyphsRemoved > Number_2) {
        warn(`Switching TrueType encoding to MacRomanEncoding for ${this.name} font`);
        encoding = Encodings.get('MacRomanEncoding');
      }

      if (hasShortCmap && this.hasEncoding && !this.isSymbolicFont) {
        let usedUnicodes: boolean[] = new Array<boolean>();

        for (let i = 0; i < glyphs.length; i++) {
          let code: number = glyphs[i].unicode;
          let gid: number = ids[i];
          glyphs[i].unicode += kCmapGlyphOffset;
          toFontChar[code] = glyphs[i].unicode;

          let glyphName: string | null = glyphNames[gid] || encoding[code];

          if (Object.keys(GlyphsUnicode).includes('glyphName')) {
            let unicode: number = GlyphsUnicode[glyphName];
            if (unicode < usedUnicodes.length) {
              continue;
            }
            usedUnicodes[unicode] = true;

            let glyphsStruct: GlyphsStruct = new GlyphsStruct();
            glyphsStruct.unicode = unicode;
            glyphsStruct.code = glyphs[i].code;
            glyphs.push(glyphsStruct)
            ids.push(gid);
            toFontChar[code] = unicode;
          }
        }
        this.useToFontChar = true;
      } else if (!this.isSymbolicFont && (this.hasEncoding || properties.glyphNames || differences.length > 0)) {
        let reverseMap: number[] = new Array<number>();

        for (let i = 0; i < glyphs.length; i++) {
          reverseMap[glyphs[i].unicode] = i;
        }

        let newGlyphUnicodes: number[] = reppeadNumberCount(glyphs.length);

        for (let i = 0; i < glyphs.length; i++) {
          let code: number = glyphs[i].unicode;
          let changeCode: boolean = false;
          let gid: number = ids[i];

          let glyphName: string = glyphNames[gid];
          if (glyphName !== null && glyphName !== undefined){
            glyphName = differences[code] || encoding[code];
            changeCode = true;
          }

          if (Object.keys(GlyphsUnicode).includes('glyphName')) {
            let unicode: number = GlyphsUnicode[glyphName];
            if (!unicode || reverseMap[unicode] === i) {
              continue;
            }

            newGlyphUnicodes[i] = unicode;
            if (changeCode){
              toFontChar[code] = unicode;
            }
            reverseMap[code] = null;
          }
        }

        for (let index = 0; index < newGlyphUnicodes.length; ++index) {
          let unicode: number = newGlyphUnicodes[index];
          if (unicode < reverseMap.length) {
            glyphs[index].unicode = unusedUnicode;
            unusedUnicode += 1;
            continue;
          }

          glyphs[index].unicode = unicode;
          reverseMap[unicode] = index;
        }
        this.useToFontChar = true;
      }


      if (this.isSymbolicFont) {
        for (let i = 0; i < glyphs.length; i++) {
          let code: number = glyphs[i].unicode & Number_0xFF;
          let fontCharCode: number = kSymbolicFontGlyphOffset | code;
          glyphs[i].unicode  = fontCharCode;
          toFontChar[code] = fontCharCode;
        }

        this.useToFontChar = true;
      }

      createGlyphNameMap(glyphs, ids, properties);
      this.glyphNameMap = properties.glyphNameMap;
    }

    cmap.data = this.createCMapTable(glyphs, ids);
    let unicodeIsEnabled: boolean[] = new Array<boolean>();

    for (let i = 0; i < glyphs.length; i++) {
      unicodeIsEnabled[glyphs[i].unicode] = true;
    }

    this.unicodeIsEnabled = unicodeIsEnabled;
    if (os2 === null || os2 === undefined){
      let pdfOverride: Map<string, number> = new Map<string, number>([
        ['unitsPerEm', this.int16([head.data[Number_18], head.data[Number_19]])], ['yMax', this.int16([head.data[Number_42], head.data[Number_43]])], ['yMin', this.int16([head.data[Number_38], head.data[Number_39]]) - Number_0x10000], ['ascent', this.int16([hhea.data[Number_4], hhea.data[Number_5]])], ['descent', this.int16([hhea.data[Number_6], hhea.data[Number_7]]) - Number_0x10000]
      ]);

      let tableEntry: TableEntry = new TableEntry()
      tableEntry.tag = 'OS/2'
      tableEntry.data = this.stringToArray(this.createOS2Table(properties, glyphs, pdfOverride))
      tables.push(tableEntry)
    }
    if (requiredTables.indexOf('post') !== -1) {
      let tableEntry: TableEntry = new TableEntry()
      tableEntry.tag = 'post'
      tableEntry.data = this.stringToArray(this.createPostTable(properties))
      tables.push(tableEntry)
    }
    if (requiredTables.indexOf('name') !== -1) {
      let tableEntry: TableEntry = new TableEntry()
      tableEntry.tag = 'name'
      tableEntry.data = this.stringToArray(this.createNameTable(this.name))
      tables.push(tableEntry)
    }

    tables.sort((a, b) => (a.tag > b.tag) ? 1 : ((b.tag > a.tag) ? -1 : 0));

    for (let i = 0; i < tables.length; i++) {
      let table: TableEntry = tables[i];
      let data: number[] = new Array<number>();

      let tableData: Uint8Array = table.data;
      for (let j = 0; j < tableData.length; j++) {
        data.push(tableData[j]);
      }

      this.createTableEntry(ttf, table.tag, new Uint8Array(data));
    }

    for (let i = 0; i < tables.length; i++) {
      let table: TableEntry = tables[i];
      let tableData: Uint8Array = table.data;
      ttf.file += this.arrayToString(tableData);

      while (ttf.file.length & Number_3) {
        ttf.file += String.fromCharCode(0);
      }
    }

    return this.stringToArray(ttf.file);
  }


  convert(fontName: string, font: Type1Font | CFFFont, properties: FontProperties): Uint8Array {

    let isFixedPitch = (glyphs: GlyphsStruct[]): boolean => {
      if (glyphs.length > 0){
        for (let i = 0; i < glyphs.length - 1; i++) {
          if (glyphs[i] !== glyphs[i + 1]) {
            return false;
          }
        }
      }
      return true;
    };

    let fontFieldsHead = (): Uint8Array => {
      let str: string = '\x00\x01\x00\x00' +
          '\x00\x00\x10\x00' +
          '\x00\x00\x00\x00' +
          '\x5F\x0F\x3C\xF5' +
          '\x00\x00' +
          '\x03\xE8' +
          '\x00\x00\x00\x00\x9e\x0b\x7e\x27' +
          '\x00\x00\x00\x00\x9e\x0b\x7e\x27' +
          '\x00\x00' +
        this.safeString16(properties.descent) +
          '\x0F\xFF' +
        this.safeString16(properties.ascent) +
        this.string16(properties.italicAngle ? Number_2 : 0) +
          '\x00\x11' +
          '\x00\x00' +
          '\x00\x00' +
          '\x00\x00';
      return this.stringToArray(str)
    };

    let fontFieldsHhea = (): Uint8Array => {
      let str: string = '\x00\x01\x00\x00' +
        this.safeString16(properties.ascent) +
        this.safeString16(properties.descent) +
          '\x00\x00' +
          '\xFF\xFF' +
          '\x00\x00' +
          '\x00\x00' +
          '\x00\x00' +
        this.safeString16(properties.capHeight) +
        this.safeString16(Math.tan(properties.italicAngle) *
        properties.xHeight) +
          '\x00\x00' +
          '\x00\x00' +
          '\x00\x00' +
          '\x00\x00' +
          '\x00\x00' +
          '\x00\x00' +
        this.string16(charstrings.length + 1);
      return this.stringToArray(str)
    };

    let fontFieldsHmtx = (): Uint8Array => {
      let hmtx: string = '\x00\x00\x00\x00';

      for (let i = 0; i < charstrings.length; i++) {
        let charstring:GlyphsStruct = charstrings[i];
        let width:number =  charstring.width ?? 0;
        hmtx += this.string16(width) + this.string16(0);
      }
      return this.stringToArray(hmtx);
    };

    let fontFieldsMaxp = (): Uint8Array => {
      return this.stringToArray('\x00\x00\x50\x00' +
        this.string16(charstrings.length + 1)); 
    }

    let kRequiredTablesCount: number = Number_9;

    let otf = new OTF();

    this.createOpenTypeHeader('\x4F\x54\x54\x4F', otf, kRequiredTablesCount);

    let charstrings: GlyphsStruct[] = font.charstrings;

    properties.fixedPitch = isFixedPitch(charstrings);

    let glyphNameMap: Map<string,number> = new Map();
    for (let i = 0; i < charstrings.length; ++i) {
      let charstring: GlyphsStruct = charstrings[i];
      glyphNameMap.set(charstring.glyph, charstring.unicode);
    }
    this.glyphNameMap = glyphNameMap;

    if (!properties.hasEncoding && (properties.subtype == 'Type1C' ||
      properties.subtype == 'CIDFontType0C')) {
      let encoding: string[] = repeatStringCont(charstrings.length);
      for (let i = 0; i < charstrings.length; ++i) {
        let charstring: GlyphsStruct = charstrings[i];
        encoding[charstring.code] = charstring.glyph;
      }
      properties.baseEncoding = encoding;
    }

    if (properties.subtype == 'CIDFontType0C') {
      let toFontChar: number[] = new Array<number>();
      for (let i = 0; i < charstrings.length; ++i) {
        let charstring:  GlyphsStruct = charstrings[i];
        toFontChar[charstring.code] = charstring.unicode;
      }
      this.toFontChar = toFontChar;
    }

    let fields: Map<string, Uint8Array> = new Map()
    fields.set('CFF', font.data);
    fields.set("OS/2", this.stringToArray(this.createOS2Table(properties, charstrings)));
    fields.set('cmap', this.createCMapTable(charstrings.slice(), (font as CFFFont).glyphIds));
    fields.set('head', fontFieldsHead());
    fields.set('hhea', fontFieldsHhea());
    fields.set('hmtx', fontFieldsHmtx());
    fields.set('maxp', fontFieldsMaxp());
    fields.set('name', this.stringToArray(this.createNameTable(fontName)));
    fields.set('post', this.stringToArray(this.createPostTable(properties)));



    for (let field of fields.keys()) {
      this.createTableEntry(otf, field, fields.get(field));
    }

    for (let field of fields.keys()) {
      let table: Uint8Array = fields.get(field);
      otf.file += this.arrayToString(table);
    }

    return this.stringToArray(otf.file);
  }

  buildToFontChar(toUnicode: number[]): number[] {
    let result: number[] = new Array<number>();
    let unusedUnicode: number = kCmapGlyphOffset;

    for (let i = 0; i < toUnicode.length; i++) {
      let unicode: number = toUnicode[i];
      let fontCharCode: number = unicode;
      result.push(fontCharCode);

    }

    return result;
  }

  rebuildToUnicode(properties: FontProperties):void {
    let firstChar: number = properties.firstChar;
    let lastChar: number = properties.lastChar;
    let map: number[] = new Array<number>();

    if (properties.composite) {
      let isIdentityMap: boolean = this.cidToUnicode.length == 0;
      for (let i = firstChar; i <= lastChar; i++) {

        let cid: number = i;
        map[i] = isIdentityMap ? cid : this.cidToUnicode[cid];

      }
    } else {
      for (let i = firstChar; i <= lastChar; i++) {
        let glyph: string = properties.differences[i];
        if (glyph === null || glyph === undefined) {
          glyph = properties.baseEncoding[i];
        }

        if (glyph !== null && glyph !== undefined && (glyph in GlyphsUnicode)) {
          map[i] = GlyphsUnicode[glyph];
        }
      }
    }

    this.toUnicode = map;
  }

  loadCidToUnicode(properties: FontProperties): void {
    if (properties.cidSystemInfo === null || properties.cidSystemInfo === undefined){
      return;
    }

    let cidToUnicodeMap: number[] = new Array<number>();
    let unicodeToCIDMap: number[] = new Array<number>();
    this.cidToUnicode = cidToUnicodeMap;
    this.unicodeToCID = unicodeToCIDMap;

    let cidSystemInfo: CidSystemInfoStruct = properties.cidSystemInfo;

    let cidToUnicode: number[][] | number[] | Record<string,object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>[] | null = null;

    if (cidSystemInfo !== null && cidSystemInfo !== undefined) {

      cidToUnicode = CIDToUnicodeMaps[`${cidSystemInfo.registry}-${cidSystemInfo.ordering}`];
    }

    if (cidToUnicode === null || cidToUnicode === undefined){
      return;
    }

    let cid: number = 1;
    let k: number;
    for (let i: number = 0; i < cidToUnicode.length; ++i) {
      let unicode:number | number[] | Record<string, string | number | boolean | object | number[] | object[] | boolean[] | string[] | Dict | Stream | Name> = cidToUnicode[i];
      if (isArray(unicode)) {
        let unicodeArray: number[] = unicode as number[];

        for (let j: number = 0; j < unicodeArray.length; j++) {
          cidToUnicodeMap[cid] = unicode[j];
          unicodeToCIDMap[unicode[j]] = cid;
        }
        cid += 1;

      } else if (typeof unicode === 'object') {
        let fillLength: number = unicode["f"];
        if (fillLength !== null && fillLength !== undefined) {
          k = unicode["c"];
          for (let j = 0; j < fillLength; ++j) {
            cidToUnicodeMap[cid] = k;
            unicodeToCIDMap[k] = cid;
            cid += 1;
            k += 1;
          }
        } else {
          cid += unicode["s"];
        }
      } else if (typeof unicode === 'number') {
        cidToUnicodeMap[cid] = unicode;
        unicodeToCIDMap[unicode] = cid;
        cid += 1 ;
      } else
        cid += 1;
    }
  }

  bindDOM(data: string): string {
    let fontName: string = this.loadedName;
    let url: string = `url(data:${this.mimetype};base64,${PdfJS_window.btoa(data)});`
    let rule: string = `@font-face { font-family:'${fontName}'; src:${url} }`

    let styleElement: PDFElement = PdfJS_window.document.createElement('style');
    PdfJS_window.document.documentElement.getElementsByTagName('head')[0].appendChild(styleElement);

    let styleSheet: PDFElementSheet = styleElement.sheet;
    styleSheet.insertRule(rule, styleSheet.cssRules.length);

    if (PDFJS.pdfBug && FontInspector.enabled) {
      FontInspector.fontAdded(this, url);
    }
    return rule;
  }

  get spaceWidth(): number {

    let possibleSpaceReplacements: string[] = ['space', 'minus', 'one', 'i'];
    let width: number;
    for (let i = 0; i < possibleSpaceReplacements.length; i++) {
      let glyphName: string = possibleSpaceReplacements[i];

      if (glyphName === typeof this.widths) {
        width = (this.widths as Map<string,number>).get(glyphName);
        break;
      }

      let glyphUnicode: number = GlyphsUnicode[glyphName];

      let charcode: number = 0;
      if (this.composite) {
        charcode = this.unicodeToCID[glyphUnicode];
      }

      if((charcode === null || charcode === undefined) && Object.keys(this).includes("toUnicode")) {
        charcode = this.toUnicode.indexOf(glyphUnicode);
      }

      if (!(charcode > 0)) {
        charcode = glyphUnicode;
      }

      width = this.widths[charcode];

      if (width > 0){
        break;
      }

    }
    width = (width || this.defaultWidth) * this.widthMultiplier;
    return width;
  }
  charToGlyph(charcode: number): GlyphsStruct {
    let fontCharCode: number = 0;
    let width: number = this.widths[charcode] = 0;
    if (charcode < (this.widths as number[]).length){
      width = (this.widths as number[])[charcode];
    }
    let operatorList: OperatorList | null = null;
    let disabled: boolean = false;

    switch (this.type) {
      case 'CIDFontType0':
        if (this.noUnicodeAdaptation) {
          width = this.widths[this.unicodeToCID[charcode] || charcode];
          fontCharCode = mapPrivateUseChars(charcode);
          break;
        }
        fontCharCode = this.toFontChar[charcode] || charcode;
        break;
      case 'CIDFontType2':
        if (this.noUnicodeAdaptation) {
          width = this.widths[this.unicodeToCID[charcode] || charcode];
          fontCharCode = mapPrivateUseChars(charcode);
          break;
        }
        fontCharCode = this.toFontChar[charcode] || charcode;
        break;
      case 'Type1':
        let glyphName:string = this.differences[charcode] || this.encoding[charcode];
        if (!isNum(width)) {

          width = (this.widths as Map<string, number>).get(glyphName);

        }
        if (this.noUnicodeAdaptation) {
          fontCharCode = mapPrivateUseChars(GlyphsUnicode[glyphName] ||
            charcode);
          break;
        }

        fontCharCode = this.glyphNameMap.get(glyphName) || GlyphsUnicode[glyphName] || charcode;
        break;
      case 'Type3':
        glyphName = this.differences[charcode] || this.encoding[charcode];
        operatorList = this.charProcOperatorList.get(glyphName);
        fontCharCode = charcode;
        break;
      case 'TrueType':
        if (this.useToFontChar) {
          fontCharCode = this.toFontChar[charcode] || charcode;
          break;
        }
        glyphName = this.differences[charcode] || this.encoding[charcode];
        if (glyphName === null || glyphName === undefined) {
          glyphName = Encodings.get('StandardEncoding')[charcode];
        }
        if (!isNum(width)) {
          width = (this.widths as Map<string, number>).get(glyphName);
        }
        if (this.noUnicodeAdaptation) {
          fontCharCode = GlyphsUnicode[glyphName] || charcode;
          break;
        }
        if (!this.hasEncoding || this.isSymbolicFont) {
          fontCharCode = this.useToFontChar ? this.toFontChar[charcode] :
            charcode;
          break;
        }

        fontCharCode = this.glyphNameMap.has(glyphName) ?
          this.glyphNameMap.get(glyphName) : GlyphsUnicode[glyphName];
        break;
      default:
        warn(`Unsupported font type: ${this.type}`);
        break;
    }

    let unicodeChars: number | string;

    if (Object.keys(this).includes("toUnicode")) {
      unicodeChars = this.toUnicode[charcode] || charcode;
    } else {
      unicodeChars = charcode;
    }
    if (typeof unicodeChars === 'number') {
      unicodeChars = String.fromCharCode(unicodeChars);
    }

    width = (isNum(width) ? width : this.defaultWidth) * this.widthMultiplier;
    disabled = this.unicodeIsEnabled ?
        !this.unicodeIsEnabled[fontCharCode] : false;

    let glyphsStruct: GlyphsStruct = new GlyphsStruct();
    glyphsStruct.unicode = Number.parseInt(unicodeChars as string);
    glyphsStruct.fontChar = String.fromCharCode(fontCharCode);
    glyphsStruct.width = width;
    glyphsStruct.disabled = disabled;
    glyphsStruct.operatorList = operatorList;
    return glyphsStruct
  }

  charsToGlyphs(chars: string): GlyphsStruct[]{
    let charsCache: Map<string,GlyphsStruct[]> = this.charsCache;
    let glyphs: GlyphsStruct[] = new Array<GlyphsStruct>();


    if (charsCache !== null && charsCache !== undefined) {
      glyphs = charsCache.get(chars);
      if (glyphs !== null && glyphs !== undefined){
        return glyphs;
      }
    }
    if (charsCache === null || charsCache === undefined){
      charsCache = this.charsCache || new Map()
      this.charsCache = charsCache
    }
    glyphs = new Array<GlyphsStruct>();
    if (this.wideChars) {

      let i:number = 0;
      let length: number = chars.length - 1;

      while (i < length) {
        let code: number = this.int16([chars.charCodeAt(i), chars.charCodeAt(i)]);
        let glyph: GlyphsStruct = this.charToGlyph(code);
        glyphs.push(glyph);
        if (code == Number_0x20) {
          glyphs.push(null);
        }
        i += Number_2;
      }
    } else {
      for (let i = 0; i < chars.length; i++) {
        let char: string = chars.charAt(i);
        let code: number = char.charCodeAt(0);
        let glyph: GlyphsStruct = this.charToGlyph(code);
        glyphs.push(glyph);

        if (code === Number_0x20) {
          glyphs.push(null);
        }
      }
    }

    charsCache.set(chars, glyphs);
    return glyphs;
  }
}

class DecodePdf {
  charstring: (number|string)[];
  width: number;
  lsb: number;
  constructor(charstring: (number|string)[], width: number, lsb: number) {
    this.charstring = charstring;
    this.width = width;
    this.lsb = lsb;
  }
}

class ProgramStruct {
  subrs: (number|string)[][];
  charstrings:GlyphsStruct[];
  properties: Map<string, Map<string, number[]|number>>;

  constructor(subrs: (number|string)[][] , charstrings:GlyphsStruct[] ,properties:Map<string, Map<string, number>>) {
    this.subrs = subrs;
    this.charstrings = charstrings;
    this.properties = properties;
  }
}

class Type1Parser {

  kEexecEncryptionKey:number = 55665;
  kCharStringsEncryptionKey:number = 4330;

  decrypt(stream: Uint8Array, key: number, discardNumber: number): Uint8Array {
    let r: number = key;
    let c1: number = 52845;
    let c2: number = 22719;
    let decryptedString: number[] = new Array<number>();

    let count: number = stream.length;
    for (let i = 0; i < count; i++) {
      let value:number = stream[i];
      decryptedString.push(value ^ (r >> Number_8));
      r = ((value + r) * c1 + c2) & ((1 << Number_16) - 1);
    }
    let result:number[] = new Array<number>();
    if (discardNumber < decryptedString.length) {
      result = decryptedString.slice(discardNumber);
    }
    return new Uint8Array(result);
  }


  static charStringDictionary:  Record<string, string | null | number | Record<string, string | null | number>> = {
    '1': 'hstem',
    '3': 'vstem',
    '4': 'vmoveto',
    '5': 'rlineto',
    '6': 'hlineto',
    '7': 'vlineto',
    '8': 'rrcurveto',
    '9': null,
    '10': 'callsubr',
    '11': 'return',
    '12': {
      '0': null,
      '1': 'vstem',
      '2': 'hstem',
      '6': -1,
      '7': -1,
      '11': 'sub',
      '12': 'div',
      '16': 'callothersubr',
      '17': 'pop',
      '33': null
    },
    '13': 'hsbw',
    '14': 'endchar',
    '21': 'rmoveto',
    '22': 'hmoveto',
    '30': 'vhcurveto',
    '31': 'hvcurveto'
  };

  kEscapeCommand:number = Number_12;

  decodeCharString(array: Uint8Array): DecodePdf {
    let charstring: (number|string)[] = new Array<number|string>();
    let lsb: number = 0;
    let width: number = 0;
    let flexState: number = 0;

    let i:number = -1;
    while ( i < array.length) {
      i += 1;
      if (i >= array.length){
        break;
      }

      let value: number = array[i];
      let escapes: number = 0;
      if (value < Number_32) {
        let command: string|number|null = null;

        if (value === this.kEscapeCommand) {
          escapes = array[i+1];
          i += 1;

          if (escapes === Number_16) {
            let index: number | string = charstring.pop();
            let argc: number | string = charstring.pop();
            for (let j = 0; j < argc; j++) {
              charstring.push('drop');
            }

            if (index < Number_3) {
              continue;
            }

            if (index === Number_3) {
              charstring.push(Number_3);
              i += 1;
              continue;
            }
          } else if (escapes == Number_17 || escapes == Number_33) {
            continue;
          }
          else if (!kHintingEnabled && (escapes == 1 || escapes == Number_2)) {

            charstring.push('drop', 'drop', 'drop', 'drop', 'drop', 'drop');
            continue;
          }

          command = Type1Parser.charStringDictionary['12'][escapes];
        } else {

          if (value == Number_13) {
            if (charstring.length == Number_2) {
              lsb = charstring[0] as number;
              width = charstring[1] as number;
              charstring.splice(0, 1);
            } else if (charstring.length == Number_4 && charstring[Number_3] == 'div') {
              lsb = charstring[0] as number;
              width = Number(charstring[1]) / Number(charstring[Number_2]);
              charstring.splice(0, 1);
            } else if (charstring.length == 4 && charstring[Number_2] == 'div') {
              lsb = Number(charstring[0]) / Number(charstring[1]) ;
              width = charstring[Number_3] as number;
              charstring.splice(0, Number_3);
            } else {
              error(`Unsupported hsbw format: ${charstring}`);
            }
            charstring.push(lsb, 'hmoveto');
            continue;
          }
          else if (value == Number_10) {
            if (charstring[charstring.length - 1] < Number_3) {
              let subrNumber:number = charstring.pop() as number;
              switch (subrNumber) {
                case 1:
                  flexState = 1;
                  break;
                case Number_2:
                  flexState = Number_2;
                  break;
                case 0:
                  charstring.push('exch', 'drop', 'exch', 'drop');
                  charstring.push('flex');
                  flexState = 0;
                  break;
              }
              continue;
            }
          } else if (value == Number_21 && flexState > 0) {
            if (flexState > 1) {
              continue;
            }
            value = Number_5;
          } else if (!kHintingEnabled && (value == 1 || value == Number_3)) {

            charstring.push('drop', 'drop');
            continue;
          }
          command = Type1Parser.charStringDictionary[value] as string|number|null;
        }

        if ((command === null || command === undefined) && i < array.length) {
          continue;
        }
        else if (command === null || command === undefined) {
          break;
        }
        else if (command == -1) {
          warn(`Support for Type1 command ${value} (${escapes}) is not implemented in charstring: ${charstring}`);
          if (value === Number_12) {

            switch (escapes) {
              case Number_7:
                charstring.push('drop', 'drop', 'drop', 'drop');
                continue;
              case Number_8:
                charstring.push('drop');
                continue;
              default:
                break;
            }
          }
        }
        charstring.push(command);
        continue;
      }
      else if (value <= Number_246) {
        value = value -Number_139;
      }
      else if (value <= Number_250) {
        let valueTemp:number = (value - Number_247) * Number_256;
        value = valueTemp + array[i+1] +Number_108;
        i += 1;
      }
      else if (value <= Number_254) {
        let valueTemp:number = -((value - Number_251) * Number_256)
        value =  valueTemp - array[i+1] -Number_108;
        i += 1;
      }
      else {
        value = (array[i+1] & Number_0xff) << Number_24 | (array[i+Number_2] & Number_0xff) << Number_16 | (array[i+ Number_3] & Number_0xff) << Number_8 | (array[i+ Number_4] & Number_0xff) << 0;
        i += Number_4;
      }
      charstring.push(value);

    }

    return new DecodePdf( charstring, width, lsb );
  }


  readNumberArray(str: string, index: number): number[] {
    let start:number = index;
    index = index
    str = str 
    while (str[index] !== '[') {
      start += 1;
      index += 1;
    }
    start += 1;

    let count:number = 0;
    while (str[index] !== ']') {
      count += 1;
      index += 1;
    }
    str = str.substring(start, start + count);

    while (str.startsWith(" ") || str.startsWith("\n")) {
  	str = str.trim();
    }
    while (str.startsWith(" ") || str.startsWith("\n")) {
      	str = str.trim();
    }
    
    let array: string[] = str.split(' ');
    let floatArray:number[] = new Array<number>();
    for (let i = 0; i < array.length; i++) {
      floatArray.push(Number.parseFloat(array[i] || '0'));
    }
    return floatArray;

  }

  readNumber(str: string, index: number): number {
    index = index
    while (str[index] == ' ') {
      index += 1;
    }
    let start:number = index;

    let count:number = 0;
    while (str[index] !== ' ') {
      index += 1;
      count += 1;
    }
    return Number.parseFloat(str.substring(start, start + count) || '0');
  }

  isSeparator(c: string): boolean {
    return c === ' ' || c === '\n' || c === '\r';
  }

  extractFontProgram(stream: Uint8Array): ProgramStruct {
    let eexec: Uint8Array = this.decrypt(stream,this.kEexecEncryptionKey, Number_4);
    let eexecStr:string = '';
    for (let i = 0; i < eexec.length; i++) {
      eexecStr += String.fromCharCode(eexec[i]);
    }

    let glyphsSection: boolean = false;
    let subrsSection: boolean = false;

    let properties = new Map<string, Map<string, number>>();
    let privateData = new Map<string, number>();
    privateData.set('lenIV', Number_4);
    properties.set('privateData', privateData);
    let program:ProgramStruct = new ProgramStruct([], [],properties);
    let glyph: string = '';
    let token: string = '';
    let length: number = 0;

    let count: number = eexecStr.length;
    let i:number = -1;
    while ( i < count) {
      i += 1;
      if (i >= count) {
        break;
      }
      let getToken = () => {
        while (i < count && this.isSeparator(eexecStr[i])) {
          i += 1;
        }
        let token:string = '';
        while (i < count && !this.isSeparator(eexecStr[i])) {
          token += eexecStr[i];
          i += 1;
        }
        return token;
      };
      let c: string = eexecStr[i];

      if ((glyphsSection || subrsSection) &&
        (token === 'RD' || token === '-|')) {
        i += 1;
        let data: Uint8Array = eexec.slice(i, i + length);
        let lenIV: number = (program.properties.get('privateData')).get('lenIV') as number;
        let encoded: Uint8Array = this.decrypt(data, this.kCharStringsEncryptionKey, lenIV);
        let str: DecodePdf = this.decodeCharString(encoded);

        if (glyphsSection) {
          let glyphsStruct: GlyphsStruct = new GlyphsStruct();
          glyphsStruct.glyph = glyph;
          glyphsStruct.width = str.width;
          glyphsStruct.data = str.charstring;
          glyphsStruct.lsb = str.lsb;

          program.charstrings.push(glyphsStruct)
        } else {
          program.subrs.push(str.charstring);
        }
        i += length;
        token = '';
      } else if (this.isSeparator(c)) {
        length = Number.parseInt(token, Number_10);
        token = '';
      } else {
        token += c;
        if (!glyphsSection) {
          switch (token) {
            case '/CharString':
              glyphsSection = true;
              break;
            case '/Subrs':
              i += 1;
              let num: number = Number.parseInt(getToken(), Number_10);
              getToken();
              let j:number = 0;
              while( j < num) {
                let t: string = getToken();
                if (t == 'ND' || t == '|-' || t == 'noaccess') {
                  break;
                }
                let index:number = Number.parseInt(getToken(), Number_10);
                if (index > j) {
                  j = index;
                }
                let length:number = Number.parseInt(getToken());
                getToken();
                let data: Uint8Array = eexec.slice(i + 1, i + 1 + length);
                let lenIV: number = program.properties.get('privateData')?.get('lenIV') as number;
                let encoded: Uint8Array = this.decrypt(data, this.kCharStringsEncryptionKey, lenIV);
                let str:DecodePdf = this.decodeCharString(encoded);
                i = i + 1 + length;
                t = getToken();
                if (t === 'noaccess'){
                  getToken();
                }
                program.subrs.push(str.charstring);
                j += 1;
              }
              break;
            case '/BlueValues':
            case '/OtherBlues':
            case '/FamilyBlues':
            case '/FamilyOtherBlues':
            case '/StemSnapH':
            case '/StemSnapV':
              program.properties.get('privateData').set(token.substring(1),this.readNumberArray(eexecStr, i + 1));
              break;
            case '/StdHW':
            case '/StdVW':
              program.properties.get('privateData').set(token.substring(1),this.readNumberArray(eexecStr, i + Number_2)[0]);
              break;
            case '/BlueShift':
            case '/lenIV':
            case '/BlueFuzz':
            case '/BlueScale':
            case '/LanguageGroup':
            case '/ExpansionFactor':
              program.properties.get('privateData').set(token.substring(1), this.readNumber(eexecStr, i + 1));
              break;
          }
        } else if (c == '/') {
          token = '';
          glyph = '';
          while (eexecStr[i] !== ' ') {
            glyph += eexecStr[i];
            i += 1;
          }
        }
      }
    }
    return program;
  }

  extractFontHeader(stream: Uint8Array, properties: FontProperties) {
    let headerString:string = '';
    for (let i = 0; i < stream.length; i++) {
      headerString += String.fromCharCode(stream[i]);
    }

    let token:string = '';
    let count:number = headerString.length;
    let i:number = 0;
    while ( i < count) {
      let getToken = ():string => {
        let character:string = headerString[i];
        while (i < count && (this.isSeparator(character) || character == '/')) {
          i += 1;
          character = headerString[i];
        }

        let token:string = '';
        while (i < count && !(this.isSeparator(character) || character == '/')) {
          token += character;
          i += 1;
          character = headerString[i];
        }

        return token;
      };

      let c: string = headerString[i];
      if (this.isSeparator(c)) {
        switch (token) {
          case '/FontMatrix':
            let matrix: number[] = this.readNumberArray(headerString, i + 1);
            for (let j = 0; j < matrix.length; j++) {
              matrix[j] *= 1000;
            }


            matrix[Number_2] *= -1;

            properties.fontMatrix = matrix;
            break;
          case '/Encoding':
            let encodingArg:string = getToken();
            let encoding: string[]|null = null;

            if (!new RegExp('^\d+$').test(encodingArg)) {
              encoding = Encodings.get(encodingArg);
            } else {
              encoding = new Array<string>();
              let size:number = Number.parseInt(encodingArg, Number_10);
              getToken();

              for (let j = 0; j < size; j++) {
                let token:string = getToken();
                if (token == 'dup') {
                  let index:number = Number.parseInt(getToken(), Number_10);
                  let glyph:string = getToken();
                  encoding[index] = glyph;
                  getToken();
                }
              }
            }

            if (!properties.hasEncoding && encoding) {
              properties.baseEncoding = encoding;
              break;
            }
            break;
        }
        token = '';
      } else {
        token += c;
      }
      i += 1;
    }
  }
}

let CFFStandardStrings: string[] = [
  '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold'
]

let type1Parser:Type1Parser = new Type1Parser();


class Type1Font {
  charstrings: GlyphsStruct[] = new Array<GlyphsStruct>();
  data:Uint8Array | null;

  constructor(name: string, file: Stream | null, properties: FontProperties) {

    let headerBlock: Uint8Array = file.getBytes(properties.length1);
    type1Parser.extractFontHeader(headerBlock, properties);


    let eexecBlock: Uint8Array = file.getBytes(properties.length2);
    let data: ProgramStruct = type1Parser.extractFontProgram(eexecBlock);

    properties.privateData = data.properties.get('privateData');


    let charstrings: GlyphsStruct[] = this.getOrderedCharStrings(data.charstrings, properties);
    let type2Charstrings: number[][]= this.getType2Charstrings(charstrings);
    let subrs:number[][] = this.getType2Subrs(data.subrs as number[][]);

    this.charstrings = charstrings;
    this.data = this.wrap(name, type2Charstrings, this.charstrings,
      subrs, properties);
  }

  wrap(name: string, glyphs: number[][], charstrings: GlyphsStruct[], subrs: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[][], properties: FontProperties): Uint8Array {
    let createCharset = (): string => {
      let charsetString: string = '\x00';

      let count: number = glyphs.length;
      for (let i = 0; i < count; i++) {
        let index:number = CFFStandardStrings.indexOf(charstrings[i].glyph!) ?? 0;
        let strTemp:string = String.fromCodePoint((index >> Number_8) & Number_0xFF) + String.fromCodePoint(index & Number_0xFF);
        charsetString += strTemp;
      }

      return charsetString;
    }

    const cffWrapPrivate = (): string => {
      let data: string =  '\x8b\x14' + '\x8b\x15';

      let fieldMap: Map<string, string> = new Map([
        ["BlueValues", "\x06"], ["OtherBlues", "\x07"], ["FamilyBlues", "\x08"], ["FamilyOtherBlues", "\x09"], ["StemSnapH", "\x0c\x0c"], ["StemSnapV", "\x0c\x0d"], ["BlueShift", "\x0c\x0d"], ["BlueFuzz", "\x0c\x0b"], ["BlueScale", "\x0c\x09"], ["LanguageGroup", "\x0c\x011"], ["ExpansionFactor", "\x0c\x018"]
      ]);

      for (let field of fieldMap.keys()) {
        if (!properties.privateData.has(field)){
          continue;
        }

        let value: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = properties.privateData.get(field);

        if (isArray(value)) {
          let arrayValue: number[] = value as number[];
          data += this.encodeNumber(arrayValue[0]);
          for (let i = 1; i < arrayValue.length; i++) {
            data += this.encodeNumber(arrayValue[i] - arrayValue[i - 1]);
          }
        } else {
          data += this.encodeNumber(value as number);
        }
        data += fieldMap.get(field);
      }

      data += this.encodeNumber(data.length + Number_4) + '\x13';
      return data;
    }

    const cffWrapTopDict = (): string => {
      let header: string = "\x00\x01\x01\x01";
      let dict: string = '\xf8\x1b\x00' +
        '\xf8\x1c\x01' +
        '\xf8\x1d\x02' +
        '\xf8\x1e\x03' +
        '\xf8\x1f\x04' +
        '\x1c\x00\x00\x10';
      let boundingBox: number[] = properties?.bbox!;
      for (let i = 0; i < boundingBox!.length; i++) {
        dict += this.encodeNumber(boundingBox![i]);
      }
      dict += '\x05';

      let offset: number =
        fields.get("header")!.length +
        fields.get("names")!.length +
          (header.length + 1) +
          (dict.length + (Number_4 + Number_4)) +
        fields.get("strings")!.length +
        fields.get("globalSubrs")!.length;

      if (offset + fields.get("charstrings")!.length > Number_32767) {
        offset += Number_9;
      } else {
        offset += Number_7;
      }
      dict += this.encodeNumber(offset) +'\x0f';
      offset = offset + (glyphs.length * Number_2) + 1;
      dict += this.encodeNumber(offset) + '\x11';

      offset = offset + fields.get("charstrings")!.length;
      dict += this.encodeNumber(fields.get("privateData")!.length);
      dict += this.encodeNumber(offset) + '\x12';

      return header + String.fromCharCode(dict.length + 1) + dict;
    }

    let fields: Map<string, string> = new Map();
    fields.set('header', '\x01\x00\x04\x04');
    fields.set("names", this.createCFFIndexHeader([name]));
    fields.set('strings', this.createCFFIndexHeader([
      "Version 0.11", "See original notice", name, name, "Medium"
    ]));
    fields.set('globalSubrs',this.createCFFIndexHeader([]));
    fields.set('charset', createCharset());
    fields.set('charstrings', this.createCFFIndexHeader([[Number_0x8B, Number_0x0E]].concat(glyphs), true));
    fields.set('privateData', cffWrapPrivate());
    fields.set('localSubrs', this.createCFFIndexHeader(subrs as string[] | number[][], true));


    fields.set('topDict', cffWrapTopDict());

    let cff_temp: number[] = [];
    for (let key of fields.keys()) {
      let field: string = fields.get(key);
      for (let i = 0; i < field.length; i++) {
        let char: number = field.charCodeAt(i);
        cff_temp.push(char);
      }
    }
    let cff:Uint8Array = new Uint8Array(cff_temp);
    return cff;
  }

  createCFFIndexHeader(objects: string[] | number[][] | null, isByte: boolean = false): string {
    let count:number = objects.length;
    if (count == 0) {
      return '\x00\x00\x00';
    }

    let data:string = String.fromCharCode((count >> Number_8) & Number_0xFF, count & Number_0xff);
    data += '\x04';


    let relativeOffset:number = 1;
    for (let i = 0; i < count + 1; i++) {
      data += String.fromCharCode((relativeOffset >>> Number_24) & Number_0xFF,
        (relativeOffset >> Number_16) & Number_0xFF,
        (relativeOffset >> Number_8) & Number_0xFF,
        relativeOffset & Number_0xFF);


      if (objects[i] !== null && objects[i] !== undefined) {
        if (Array.isArray(objects[i])){
          relativeOffset += objects[i].length;
        }else {
          relativeOffset += objects[i].length;
        }
      }
    }

    for (let i = 0; i < count; i++) {
      let anyObject: string | number[] = objects[i];
      if (Array.isArray(anyObject)){
        for (let j = 0; j < anyObject.length; j++){
          data += isByte ? String.fromCharCode((anyObject[j] as number) & Number_0xFF) :
            anyObject[j];
        }
      }else {
        for (let j = 0; j < anyObject.length; j++){
          data += anyObject[j];
        }
      }
    }
    return data;
  }

  encodeNumber(value: number): string {
    value = value;
    value |= 0;

    if (value >= -32768 && value <= Number_32767) {
      return '\x1c' +
      String.fromCharCode((value >> Number_8) & Number_0xFF) +
      String.fromCharCode(value & Number_0xFF);
    } else {
      return '\x1d' +
      String.fromCharCode((value >> Number_24) & Number_0xFF) +
      String.fromCharCode((value >> Number_16) & Number_0xFF) +
      String.fromCharCode((value >> Number_8) & Number_0xFF) +
      String.fromCharCode(value & Number_0xFF);
    }
  }

  getOrderedCharStrings(glyphs: GlyphsStruct[], properties: FontProperties): GlyphsStruct[] {
    let charstrings: GlyphsStruct[] = new Array<GlyphsStruct>();
    let glyphName: string;
    let unusedUnicode: number = kCmapGlyphOffset;
    for (let i: number = 0; i < glyphs.length; i++) {
      let item:GlyphsStruct = glyphs[i];
      glyphName = item.glyph;
      let unicode:number = Object.keys(GlyphsUnicode).includes(glyphName) ? GlyphsUnicode[glyphName] : unusedUnicode;
      unusedUnicode = (unicode == unusedUnicode) ? (unusedUnicode + 1) : unusedUnicode;
      let glyphsStruct: GlyphsStruct = new GlyphsStruct();
      glyphsStruct.unicode = unicode;
      glyphsStruct.glyph = glyphName;
      glyphsStruct.width = item.width;
      glyphsStruct.lsb = item.lsb;
      glyphsStruct.gid = i;
      glyphsStruct.charstring = item.data;

      charstrings.push(glyphsStruct)
    }

    charstrings.sort( (a, b):number => {
      return a.unicode - b.unicode;
    });

    return charstrings;
  }

  getType2Charstrings(type1Charstrings: GlyphsStruct[]): number[][] {
    let type2Charstrings: number[][] = new Array<number[]>();
    let count: number = type1Charstrings.length;

    for (let i = 0; i < count; i++) {
      let charstring: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = type1Charstrings[i].charstring;
      type2Charstrings.push(
        this.flattenCharstring(charstring.slice() as (number|string)[], this.commandsMap)
      );
    }

    return type2Charstrings;
  }

  getType2Subrs(type1Subrs: number[][]): number[][] {
    let bias:number = 0;
    let count:number = type1Subrs.length;

    if (count < Number_1240) {
      bias =Number_107;
    } else if (count < Number_33900) {
      bias = Number_1131;
    } else {
      bias = Number_32768;
    }


    let type2Subrs: number[][] = new Array<number[]>();
    for (let i = 0; i < bias; i++) {
      type2Subrs.push([Number_0x0B]);
    }

    for (let i = 0; i < count; i++) {
      let subr:number[] = type1Subrs[i] ?? [Number_0x0B];

      type2Subrs.push(this.flattenCharstring(subr, this.commandsMap));
    }

    return type2Subrs;
  }

  commandsMap: Record<string, number | number[]> = {
    'hstem': 1,
    'vstem': 3,
    'vmoveto': 4,
    'rlineto': 5,
    'hlineto': 6,
    'vlineto': 7,
    'rrcurveto': 8,
    'callsubr': 10,
    'return': 11,
    'sub': [12, 11],
    'div': [12, 12],
    'exch': [12, 28],
    'flex': [12, 35],
    'drop': [12, 18],
    'endchar': 14,
    'rmoveto': 21,
    'hmoveto': 22,
    'vhcurveto': 30,
    'hvcurveto': 31
  };

  flattenCharstring(charstring: (number|string)[], map: Record<string, number | number[]>):number[] {
    charstring = charstring;
    let i:number = 0
    while ( i < charstring.length) {
      let command:number|string = charstring[i];

      if ( typeof command === 'string') {
        let cmd:number|number[] = map[command];

        assert(cmd !== null, `Unknown command: ${command}`);

        if (isArray(cmd)) {
          charstring.splice(i, 1, cmd[0], cmd[1]);
          i += 1;
        } else {
          charstring[i] = cmd as number ;
        }

      } else {

        if (command > Number_32000) {
          let divisor:number = charstring[i + 1] as number;
          command = command / divisor;
          charstring.splice(i, Number_3, Number_28, command >> Number_8, command & Number_0xff);
        } else {
          charstring.splice(i, 1, Number_28, command >> Number_8, command & Number_0xff);
        }
        i += Number_2;
      }

      i += 1;
    }
    let charstringInt: number[] = charstring.filter((item) => typeof item === 'number') as number[];

    return charstringInt;
  }


}

class CFFFont {
  properties: FontProperties | null;
  data: Uint8Array | null = null;
  charstrings: GlyphsStruct[] | null = null;
  glyphIds: number[] | null = null;

  constructor(file: Stream | Uint8Array, properties: FontProperties) {
    this.properties = properties;

    let parser: CFFParser = new CFFParser(file as Stream, properties);
    let cff: CFF = parser.parse();
    let compiler: CFFCompiler = new CFFCompiler(cff);
    this.readExtra(cff);
    try {
      this.data = compiler.compile();
    } catch (e) {
      warn(`Failed to compile font ${properties.loadedName}`);
      this.data = file as Uint8Array;
    }
  }

  readExtra (cff: CFF): void {
    let charset: string[] = cff.charset.charset;
    let encoding: number[] = cff.encoding ? cff.encoding.encoding : null;
    let charstrings: GlyphsStruct[] = this.getCharStrings(charset, encoding);

    let glyphIds: number[] = new Array<number>();
    for (let i = 0; i < charstrings.length; i++) {
      glyphIds.push(charstrings[i].gid);
    }

    this.charstrings = charstrings;
    this.glyphIds = glyphIds;
  }

  getCharStrings = (charsets: string[], encoding: number[] | null): GlyphsStruct[] => {
    let charstrings: GlyphsStruct[] = new Array<GlyphsStruct>();

    let unicodeUsed: (boolean | null)[] = new Array<boolean | null>();

    let unassignedUnicodeItems: number[] = new Array<number>();
    let inverseEncoding: number[] = new Array<number>();
    if (encoding !== null && encoding !== undefined){
      for (let i = 0; i < encoding.length; i++) {
        let encode: number = encoding[i];
        inverseEncoding[encoding[encode]] = encode | 0;
      }
    } else {
      inverseEncoding = charsets.map((index) => Number(index));
    }

    for (let i = 0; i < charsets.length; i++) {
      let glyph: string = charsets[i];
      if (glyph == ".notdef") {
        continue;
      }

      let code: number = inverseEncoding[i];
      if ((code === null || code === undefined) || isSpecialUnicode(code)) {
        unassignedUnicodeItems.push(i);
        continue;
      }
      let glyphsStruct: GlyphsStruct = new GlyphsStruct()
      glyphsStruct.unicode = code;
      glyphsStruct.code = code;
      glyphsStruct.glyph = glyph;
      glyphsStruct.gid = i;
      charstrings.push(glyphsStruct)
      unicodeUsed[code] = true;
    }

    let nextUnusedUnicode:number = kCmapGlyphOffset;
    for (let j = 0; j < unassignedUnicodeItems.length; ++j) {
      let i:number = unassignedUnicodeItems[j];
      while (unicodeUsed[nextUnusedUnicode] !== null && unicodeUsed[nextUnusedUnicode] !== undefined) {
        nextUnusedUnicode += 1;
      }
      let unicode:number = nextUnusedUnicode;
      nextUnusedUnicode += 1
      let glyphsStruct: GlyphsStruct = new GlyphsStruct();
      glyphsStruct.unicode = unicode;
      glyphsStruct.code = inverseEncoding[i];
      glyphsStruct.glyph = charsets[i];
      glyphsStruct.gid = i;
      charstrings.push(glyphsStruct)
    }

    charstrings.sort( (a, b):number => {
      return a.unicode - b.unicode;
    });
    return charstrings;
  }


}

class ParseHeader {
  obj: CFFHeader;
  endPos: number;
  constructor(obj: CFFHeader, endPos: number) {
    this.obj = obj;
    this.endPos = endPos;
  }
}

class ParseIndex {
  obj: CFFIndex;
  endPos: number;
  constructor(obj: CFFIndex, endPos: number) {
    this.obj = obj;
    this.endPos = endPos;
  }
}

class CFFParser {
  bytes: Uint8Array;
  properties: FontProperties;
  cff: CFF | null = null ;

  constructor(file: Stream, properties: FontProperties) {
    this.bytes = file.getBytes();
    this.properties = properties;
  }

  parse = (): CFF => {
    let properties: FontProperties = this.properties;
    let cff:CFF = new CFF();
    this.cff = cff;


    let header:ParseHeader = this.parseHeader();
    let nameIndex:ParseIndex = this.parseIndex(header.endPos);
    let topDictIndex:ParseIndex = this.parseIndex(nameIndex.endPos);
    let stringIndex:ParseIndex = this.parseIndex(topDictIndex.endPos);
    let globalSubrIndex:ParseIndex = this.parseIndex(stringIndex.endPos);

    let topDictParsed = this.parseDict(topDictIndex.obj.get(0));
    let topDict:CFFDict = this.createDict("CFFTopDict", topDictParsed, cff.strings);

    cff.header = header.obj;
    cff.names = this.parseNameIndex(nameIndex.obj);
    cff.strings = this.parseStringIndex(stringIndex.obj);
    cff.topDict = topDict;
    cff.globalSubrIndex = globalSubrIndex.obj;

    this.parsePrivateDict(cff.topDict as CFFTopDict);

    cff.isCIDFont = topDict.hasName('ROS');


    let charStringOffset: number | number[] = topDict.getByName('CharStrings');
    cff.charStrings = this.parseCharStrings(charStringOffset as number);

    let charset:CFFCharset;
    let encoding:CFFEncoding | null;
    if (cff.isCIDFont) {
      let fdArrayIndex: CFFIndex = this.parseIndex(topDict.getByName('FDArray') as number).obj;
      for (let i = 0; i < fdArrayIndex.count; ++i) {
        let dictRaw: Uint8Array = fdArrayIndex.get(i);
        let fontDict: CFFDict = this.createDict("CFFTopDict", this.parseDict(dictRaw),
          cff.strings);
        this.parsePrivateDict(fontDict as CFFTopDict);
        cff.fdArray.push(fontDict as CFFTopDict);
      }

      encoding = null;
      charset = this.parseCharsets(topDict.getByName('charset')  as number, cff.charStrings.count, cff.strings, true);
      cff.fdSelect = this.parseFDSelect(topDict.getByName('FDSelect')  as number, cff.charStrings.count);
    } else {
      charset = this.parseCharsets(topDict.getByName('charset') as number, cff.charStrings.count, cff.strings, false);
      encoding = this.parseEncoding(topDict.getByName('Encoding') as number, properties, cff.strings, charset.charset);
    }
    cff.charset = charset;
    cff.encoding = encoding;

    return cff;
  }

  parseHeader = ():ParseHeader => {
    let offset:number = 0;

    while (this.bytes[offset] !== 1) {
      offset += 1;
    }

    if (offset !== 0) {
      warn('cff data is shifted');
      this.bytes = this.bytes.subarray(offset);
    }

    let major: number = this.bytes[0];
    let minor: number = this.bytes[1];
    let hdrSize: number =this.bytes[Number_2];
    let offSize: number = this.bytes[Number_3];
    let header: CFFHeader = new CFFHeader(major, minor, hdrSize, offSize);
    return new ParseHeader (header,  hdrSize);
  }

  parseDict(dict: Uint8Array): (number|number[])[][]{
    let pos: number = 0;

    let parseOperand = (): number => {
      let value:number = dict[pos];
      pos += 1;

      if (value === Number_30) {
        return parseFloatOperand();
      } else if (value === Number_28) {
        value = (value << Number_8) | dict[pos];
        pos += 1;
        return value;
      } else if (value === Number_29) {
        value = (value << Number_8) | dict[pos];
        value = (value << Number_8) | dict[pos + 1];
        value = (value << Number_8) | dict[pos + Number_2];
        value = (value << Number_8) | dict[pos + Number_3];
        pos += Number_4;
        return value;
      } else if (value >= Number_32 && value <= Number_246) {
        return value -Number_139;
      } else if (value >= Number_247 && value <= Number_250) {
        let result: number = ((value - Number_247) * Number_256) + dict[pos] +Number_108;
        pos += 1;
        return result;
      } else if (value >= Number_251 && value <= Number_254) {
        let valuetemp:number = -((value - Number_251) * Number_256) ;
        let result: number = valuetemp - dict[pos] -Number_108;
        pos += 1;
        return result;
      } else {
        error('255 is not a valid DICT command');
      }

      return -1;
    };

    let parseFloatOperand = (): number => {
      let str:string = '';
      let eof:number = Number_15;
      let lookup:string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-'];
      let length:number = dict.length;

      while (pos < length) {
        let b:number = dict[pos];
        let b1:number = b >> Number_4;
        let b2:number = b & Number_15;

        if (b1 === eof) {
          break;
        }
        str += lookup[b1];

        if (b2 === eof) {
          break;
        }
        str += lookup[b2];
        pos += 1;
      }

      return Number.parseFloat(str);
    };

    let operands: number[] = new Array<number>();
    let entries:(number|number[])[][] = new Array<(number|number[])[]>();


    pos = 0;
    let end:number = dict.length;
    while (pos < end) {
      let b:number = dict[pos];
      if (b <= Number_21) {
        if (b === Number_12) {
          pos += 1;
          b = (b << Number_8) | dict[pos];
        }
        entries.push([b, operands]);
        operands = new Array<number>();
        pos += 1;
      } else {
        operands.push(parseOperand());
      }
    }

    return entries;
  }

  parseIndex(pos: number):ParseIndex{
    let cffIndex: CFFIndex = new CFFIndex();
    let bytes: Uint8Array = this.bytes;
    let count: number = (bytes[pos] << Number_8) | bytes[pos + 1];
    let offsets: number[] = new Array<number>();
    let start: number = pos + Number_2;
    let end: number = pos + Number_2;

    if (count !== 0) {
      let offsetSize: number = bytes[pos+Number_2]
      let startPos: number = pos + ((count + 1) * offsetSize) - 1;

      for (let i = 0; i <  count + 1; ++i) {
        let offset:number = 0;
        for (let j = 0; j < offsetSize; ++j) {
          offset <<= Number_8;
          offset += bytes[start];
          start += 1;
        }
        offsets.push(startPos + offset);
      }
      end = offsets[count];
    }
    for (let i = 0; i < offsets.length; ++i) {
      let offsetStart:number = offsets[i];
      let offsetEnd:number = offsets[i + 1];
      cffIndex.add(bytes.subarray(offsetStart, offsetEnd));
    }
    return new ParseIndex(cffIndex,  end);
  }

  parseNameIndex = (index: CFFIndex):string[] => {
    let names: string[] = new Array<string>();
    for (let i = 0; i < index.count; ++i) {
      let name: Uint8Array = index.get(i);
      let length: number = Math.min(name.length, Number_127);
      let data: number[] = new Array<number>();
      for (let j = 0; j < length; ++j) {
        let c: number = name[j];
        if (j === 0 && c === 0) {
          data[j] = c;
          continue;
        }
        if (
          (c < Number_33 || c > Number_126) || c === Number_91  || c === Number_93  ||
            c === Number_40  || c === Number_41  || c === Number_123 || c === Number_125 ||
            c === Number_60  || c === Number_62  || c === Number_47  || c === Number_37
        ) {
          data.push(Number_95);
          continue;
        }
        data.push(c);
      }
      names.push(String.fromCharCode(...data));

    }
    return names;
  };

  parseStringIndex = (index: CFFIndex):CFFStrings => {
    let strings:CFFStrings  = new CFFStrings();
    for (let i = 0; i < index.count; ++i) {
      let data:Uint8Array = index.get(i);
      strings.add(String.fromCharCode(...data));

    }
    return strings;
  };

  createDict(type:string, dict: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[], strings: CFFStrings): CFFDict {

    let cffDict: CFFDict;
    if (type === "CFFTopDict") {
      cffDict = new CFFTopDict(strings);
    } else {
      cffDict = new CFFPrivateDict(strings);
    }
    for (let i = 0; i < dict.length; ++i) {
      let pair: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = dict[i];
      let key: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = pair[0];
      let value: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = pair[1];
      cffDict.setByKey(key as number, value as number[]);
    }
    return cffDict;
  }

  parseCharStrings = (charStringOffset: number):CFFIndex  => {
    let charStrings:CFFIndex = this.parseIndex(charStringOffset).obj;
    let count: number = charStrings.count;
    for (let i = 0; i < count; i++) {
      let charstring:Uint8Array = charStrings.get(i);
      let data:Uint8Array = charstring;
      let length: number = data.length;
      let j:number = 0;
      while ( j < length) {
        let value: number = data[j];
        j += 1;
        if (value == Number_12 && data[j+1] == 0) {
          j += 1;
          data[j - Number_2] =Number_139;
          data[j - 1] = Number_22;
        } else if (value === Number_28) {
          j += Number_2;
        } else if (value >= Number_247 && value <= Number_254) {
          j += 1;
        } else if (value == Number_255) {
          j += Number_4;
        }
      }
    }
    return charStrings;
  };

  parsePrivateDict(parentDict: CFFTopDict): void{

    if (!parentDict.hasName('Private')) {
      return
    }
    let privateOffset: number[] = parentDict.getByName('Private') as number[];
    if (!isArray(privateOffset) || privateOffset.length !== Number_2) {
      parentDict.removeByName('Private');
      return;
    }

    let size: number = privateOffset[0];
    let offset: number = privateOffset[1];


    if (size === 0 || offset >= this.bytes.length) {
      parentDict.removeByName('Private');
      return;
    }

    let privateDictEnd: number = offset + size;
    let dictData: Uint8Array = this.bytes.subarray(offset, privateDictEnd);
    let dict: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = this.parseDict(dictData);
    let privateDict: CFFPrivateDict = this.createDict("CFFPrivateDict", dict, parentDict.strings) as CFFPrivateDict;
    parentDict.privateDict = privateDict;
    if (privateDict.getByName('Subrs') === null || privateDict.getByName('Subrs') === undefined){
      return
    }

    let subrsOffset: number = privateDict.getByName('Subrs') as number;
    let relativeOffset: number = offset + subrsOffset;

    if (subrsOffset === 0 || relativeOffset >= this.bytes.length) {
      privateDict.removeByName('Subrs');
      return;
    }

    let subrsIndex:ParseIndex = this.parseIndex(relativeOffset);
    privateDict.subrsIndex = subrsIndex.obj;
  }

  parseCharsets = (pos: number, length: number, strings: CFFStrings, cid: boolean): CFFCharset => {
    if (pos === 0) {
      return new CFFCharset(true, CFFCharsetPredefinedTypes.ISO_ADOBE, ISOAdobeCharset);
    } else if (pos === 1) {
      return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT, ExpertCharset);
    } else if (pos === Number_2) {
      return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT_SUBSET, ExpertSubsetCharset);
    }

    let bytes: Uint8Array = this.bytes;
    let start: number = pos;
    let format: number = bytes[pos++];
    let charset: string[] = ['.notdef'];


    length = length - 1;

    pos = pos + 1;

    switch (format) {
      case 0:
        for (let i = 0; i < length; i++) {
          let id:number = (bytes[pos] << Number_8) | bytes[pos];
          charset.push(cid ? String(id) : strings.get(id));
          pos += Number_2;
        }
        break;
      case 1:
        while (charset.length <= length) {
          let id:number = (bytes[pos] << Number_8) | bytes[pos];
          let count:number = bytes[pos];

          for (let i = 0; i <= count; i++){
            charset.push(cid ? String(id) : strings.get(id));
            id += 1;
          }
          pos += Number_3;
        }
        break;
      case Number_2:
        while (charset.length <= length) {
          let id:number = (bytes[pos] << Number_8) | bytes[pos];
          let count:number = (bytes[pos] << Number_8) | bytes[pos];
          for (let i = 0; i <= count; i++) {
            charset.push(cid ? String(id) : strings.get(id));
            id += 1;
          }
          pos += Number_4;
        }
        break;
      default:
        error('Unknown charset format');
    }


    let end: number = pos;
    let raw:Uint8Array = bytes.subarray(start, end);

    return new CFFCharset(false, format, charset, raw);
  };

  parseEncoding = (pos: number, properties: FontProperties, strings: CFFStrings, charset: string[]): CFFEncoding => {
    let encoding: number[] = new Array<number>();
    let bytes: Uint8Array = this.bytes;
    let predefined: boolean = false;
    let hasSupplement: boolean = false;
    let format: number;
    let raw: null|Uint8Array = null;
    pos = pos;
    let readSupplement = () => {
      let supplementsCount: number = bytes[pos];
      pos += 1;
      for (let i = 0; i < supplementsCount; i++) {
        let code: number = bytes[pos];
        let sid:number = (bytes[pos+1] << Number_8) + (bytes[pos++] & Number_0xff);
        encoding[code] = properties.differences.indexOf(strings.get(sid));
        pos += Number_3;
      }
    };

    if (pos == 0 || pos == 1) {
      predefined = true;
      format = pos;
      let gid: number = 1;
      let baseEncoding: string[] = pos ? Encodings.get('ExpertEncoding') : Encodings.get('StandardEncoding');
      for (let i = 0; i < charset.length; i++) {
        let index: number = baseEncoding.indexOf(charset[i]);
        if (index !== -1)
          encoding[index] = gid;
        gid += 1;
      }
    }
    else {
      let dataStart: number = pos;
      let format: number = bytes[pos];
      pos += 1;
      switch (format & Number_0x7f) {
        case 0:
          let glyphsCount:number = bytes[pos];
          pos += 1;
          for (let i = 1; i <= glyphsCount; i++) {
            encoding[bytes[pos]] = i;
            pos += 1;
          }
          break;


        case 1:
          let rangesCount:number = bytes[pos];
          pos += 1;
          let gid:number = 1;
          for (let i = 0; i < rangesCount; i++) {
            let start = bytes[pos];
            let left = bytes[pos+1];
            pos += Number_2;
            for (let j = start; j <= start + left; j++) {
              encoding[j] = gid;
              gid += 1;
            }
          }
          break;

        default:
          error(`Unknown encoding format: ${format} in CFF`);
          break;
      }
      let dataEnd:number = pos;
      if ((format & Number_0x80) !== 0) {
        bytes[dataStart] &= Number_0x7f;
        readSupplement();
        hasSupplement = true;
      }
      raw = bytes.subarray(dataStart, dataEnd);
    }
    format = format & Number_0x7f;
    return new CFFEncoding(predefined, format, encoding, raw);
  };

  parseFDSelect = (pos: number, length: number):CFFFDSelect => {
    let start: number = pos;
    let bytes: Uint8Array = this.bytes;
    let format:number = bytes[pos];
    let fdSelect:number[] = new Array<number>();
    pos = pos + 1;
    switch (format) {
      case 0:
        for (let i = 0; i < length; ++i) {
          let id = bytes[pos];
          fdSelect.push(id);
          pos += 1;
        }
        break;
      case Number_3:
        let rangesCount:number = (bytes[pos] << Number_8) | bytes[pos+1];
        pos += Number_2;
        for (let i = 0; i < rangesCount; ++i) {
          let first:number = (bytes[pos] << Number_8) | bytes[pos+1];
          let fdIndex:number = bytes[pos+ Number_2];
          let next:number = (bytes[pos+ Number_3] << Number_8) | bytes[pos + Number_4];
          for (let j = first; j < next; ++j) {
            fdSelect.push(fdIndex);
          }
          pos += Number_3;
        }
        pos += Number_2;
        break;
      default:
        error(`Unknown fdselect format ${format}`);
        break;
    }
    let end: number = pos;
    return new CFFFDSelect(fdSelect, bytes.subarray(start, end));
  }
}

class CFF {
  header: CFFHeader|null = null;
  names: string[] = new Array<string>();
  topDict: CFFDict |null = null ;
  strings: CFFStrings = new CFFStrings() ;
  globalSubrIndex: CFFIndex|null = null;
  encoding: CFFEncoding|null = null;
  charset: CFFCharset | null = null;
  charStrings: CFFIndex | null = null;
  fdArray: CFFTopDict[] = new Array<CFFTopDict>();
  fdSelect: CFFFDSelect|null = null;
  isCIDFont: boolean = false;

}

class CFFHeader {
  major: number;
  minor: number;
  hdrSize: number;
  offSize: number;

  constructor(major: number, minor: number, hdrSize: number, offSize: number) {
    this.major = major;
    this.minor = minor;
    this.hdrSize = hdrSize;
    this.offSize = offSize;
  }
}

class CFFStrings {
  strings: string[] = new Array<string>();


  public get(index: number): string {
    if (index >= 0 && index <= 390) {
      return CFFStandardStrings[index];
    }
    if (index - 391 <= this.strings.length) {
      return this.strings[index - 391];
    }
    return CFFStandardStrings[0];
  }

  public add(value: string): void {
    this.strings.push(value);
  }

  public get count(): number {
    return this.strings.length;
  }
}

class CFFIndex {
  objects: Uint8Array[] = new Array<Uint8Array>();
  public length: number = 0;


  public add(data: Uint8Array): void {
    this.length += data.length;
    this.objects.push(data);
  }

  public get(index: number): Uint8Array {
    return this.objects[index];
  }

  public get count(): number {
    return this.objects.length;
  }
}

class TablesStruct {
  keyToNameMap: Map<number, string> = new Map();
  nameToKeyMap: Map<string, number> = new Map();
  defaults: Map<number, number[] | number> = new Map();
  types: Map<number, object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name> = new Map();
  opcodes: Map<number, number[]> = new Map();
  order: number[] = new Array<number>();
}

class CFFTopDict extends CFFDict {

  static layout: (string | string[] | number[]|null|number)[][] = [
    [[12, 30], 'ROS', ['sid', 'sid', 'num'], null], [[12, 20], 'SyntheticBase', 'num', null], [0, 'version', 'sid', null], [1, 'Notice', 'sid', null], [[12, 0], 'Copyright', 'sid', null], [2, 'FullName', 'sid', null], [3, 'FamilyName', 'sid', null], [4, 'Weight', 'sid', null], [[12, 1], 'isFixedPitch', 'num', 0], [[12, 2], 'ItalicAngle', 'num', 0], [[12, 3], 'UnderlinePosition', 'num', -100], [[12, 4], 'UnderlineThickness', 'num', 50], [[12, 5], 'PaintType', 'num', 0], [[12, 6], 'CharstringType', 'num', 2], [[12, 7], 'FontMatrix', ['num', 'num', 'num', 'num', 'num', 'num'], [.001, 0, 0, .001, 0, 0]], [13, 'UniqueID', 'num', null], [5, 'FontBBox', ['num', 'num', 'num', 'num'], [0, 0, 0, 0]], [[12, 8], 'StrokeWidth', 'num', 0], [14, 'XUID', 'array', null], [15, 'charset', 'offset', 0], [16, 'Encoding', 'offset', 0], [17, 'CharStrings', 'offset', 0], [18, 'Private', ['offset', 'offset'], null], [[12, 21], 'PostScript', 'sid', null], [[12, 22], 'BaseFontName', 'sid', null], [[12, 23], 'BaseFontBlend', 'delta', null], [[12, 31], 'CIDFontVersion', 'num', 0], [[12, 32], 'CIDFontRevision', 'num', 0], [[12, 33], 'CIDFontType', 'num', 0], [[12, 34], 'CIDCount', 'num', 8720], [[12, 35], 'UIDBase', 'num', null], [[12, 36], 'FDArray', 'offset', null], [[12, 37], 'FDSelect', 'offset', null], [[12, 38], 'FontName', 'sid', null]
  ];
  tables:TablesStruct | null = null;
  privateDict: CFFPrivateDict | null;

  constructor(strings: CFFStrings) {
    super(CFFDict.createTables(CFFTopDict.layout),strings);
    if (this.tables === null || this.tables === undefined){
      this.tables = CFFDict.createTables(CFFTopDict.layout);
    }
    this.privateDict = null;
  }
}

class CFFPrivateDict extends CFFDict {
  static layout: (string | string[] | number[]|null|number)[][] = [
    [6, 'BlueValues', 'delta', null], [7, 'OtherBlues', 'delta', null], [8, 'FamilyBlues', 'delta', null], [9, 'FamilyOtherBlues', 'delta', null], [[12, 9], 'BlueScale', 'num', 0.039625], [[12, 10], 'BlueShift', 'num', 7], [[12, 11], 'BlueFuzz', 'num', 1], [10, 'StdHW', 'num', null], [11, 'StdVW', 'num', null], [[12, 12], 'StemSnapH', 'delta', null], [[12, 13], 'StemSnapV', 'delta', null], [[12, 14], 'ForceBold', 'num', 0], [[12, 17], 'LanguageGroup', 'num', 0], [[12, 18], 'ExpansionFactor', 'num', 0.06], [[12, 19], 'initialRandomSeed', 'num', 0], [19, 'Subrs', 'offset', null], [20, 'defaultWidthX', 'num', 0], [21, 'nominalWidthX', 'num', 0]
  ];
  tables:TablesStruct | null = null;
  subrsIndex: CFFIndex|null;
  constructor(strings: CFFStrings) {
    super(CFFDict.createTables(CFFPrivateDict.layout),strings);
    if (this.tables === null || this.tables === undefined){
      this.tables = CFFDict.createTables(CFFTopDict.layout);
    }
    this.subrsIndex = null;
  }
}

enum CFFCharsetPredefinedTypes {
  ISO_ADOBE = 0,
  EXPERT = 1,
  EXPERT_SUBSET = 2,
}

enum CFFCharsetEmbeddedTypes  {
  FORMAT0 = 0,
  FORMAT1 = 1,
  FORMAT2 = 2,
}

class CFFCharset {
  predefined: boolean;
  format: number;
  charset: string[];
  raw: Uint8Array;

  constructor(predefined: boolean, format: number, charset: string[], raw?: Uint8Array) {
    this.predefined = predefined;
    this.format = format;
    this.charset = charset;
    this.raw = raw;
  }
}

let CFFEncodingPredefinedTypes: Map<string, number> = new Map([
  ["STANDARD", 0], ["EXPERT", 1]
]);

let CFFEncodingEmbeddedTypes: Map<string, number> = new Map([
  ["FORMAT0", 0], ["FORMAT1", 1]
]);

class CFFEncoding {
  predefined: boolean;
  format: number;
  encoding: number[];
  raw: Uint8Array | null;

  constructor(predefined: boolean, format: number, encoding: number[], raw: Uint8Array) {
    this.predefined = predefined;
    this.format = format;
    this.encoding = encoding;
    this.raw = raw;
  }
}

class CFFFDSelect {
  fdSelect: number[];
  raw: Uint8Array;

  constructor(fdSelect: number[], raw: Uint8Array) {
    this.fdSelect = fdSelect;
    this.raw = raw;
  }
}

class CFFOffsetTracker {
  offsets: Map<string, number> = new Map();
  isTracking(key: string): boolean {
    return this.offsets.has(key);
  }

  track(key: string, location: number): void {
    if (this.offsets.has(key)) {
      error(`Already tracking location of ${key}`);
    }
    this.offsets.set(key, location);
  }

  offset(value: number): void {
    for (let key of this.offsets.keys()) {
      this.offsets.set(key,this.offsets.get(key)+value);
    }
  }

  setEntryLocation(key: string, values: number[], output: CFFOutput): void {
    if (!(this.offsets.has(key))) {
      error(`Not tracking location of ${key}`);
    }
    let data:number[] = output.data;
    let dataOffset:number = this.offsets.get(key);
    let size:number = Number_5;
    for (let i = 0; i < values.length; ++i) {
      let offset0:number = i * size + dataOffset;
      let offset1:number = offset0 + 1;
      let offset2:number = offset0 + Number_2;
      let offset3:number = offset0 + Number_3;
      let offset4:number = offset0 + Number_4;


      if (
        data[offset0] !== Number_0x1d || data[offset1] !== 0 ||
          data[offset2] !== 0 || data[offset3] !== 0 || data[offset4] !== 0
      ) {
        error('writing to an offset that is not empty');
      }
      let value:number = values[i];
      data[offset0] = Number_0x1d;
      data[offset1] = (value >> Number_24) & Number_0xFF;
      data[offset2] = (value >> Number_16) & Number_0xFF;
      data[offset3] = (value >> Number_8) & Number_0xFF;
      data[offset4] = value & Number_0xFF;
    }
  }
}

class CFFOutput {
  data: number[] = new Array<number>();
  length: number = 0;
  add(data: number[] | Uint8Array): void {
    this.data.push(...data);
    this.length = this.data.length;
  }
}

class CFFTrackers {
  trackers: CFFOffsetTracker[];
  output: Uint8Array;

  constructor(trackers: CFFOffsetTracker[], output: Uint8Array) {
    this.trackers = trackers;
    this.output = output;
  }
}

class CFFout{
  writeByteArray(param:Uint8Array){
  }
}

class CFFCompiler {

  out: CFFout | null = null;
  private cff: CFF;

  constructor(cff: CFF) {
    this.cff = cff;
  }


  private stringToArray(str: string):Uint8Array {

    let array: Uint8Array = new Uint8Array();
    for (let i = 0; i < str.length; ++i) {
      array[i] = str.charCodeAt(i)??0;

    }
    return array;
  }



  compile(): Uint8Array {
    let cff: CFF = this.cff;
    let output: CFFOutput = new CFFOutput()


    let header: number[] = this.compileHeader(cff.header);
    output.add(header);

    let nameIndex: Uint8Array = this.compileNameIndex(cff.names);
    output.add(Array.from(nameIndex));

    let compiled: CFFTrackers = this.compileTopDicts([cff.topDict], output.length);
    output.add(Array.from(compiled.output));
    let topDictTracker: CFFOffsetTracker = compiled.trackers[0];

    let stringIndex: number[] = this.compileStringIndex(cff.strings.strings);
    output.add(stringIndex);

    let globalSubrIndex: Uint8Array = this.compileIndex(cff.globalSubrIndex);
    output.add(Array.from(globalSubrIndex));

    if ((cff.encoding !== null && cff.encoding !== undefined) && cff.topDict.hasName('Encoding')) {
      if (cff.encoding.predefined) {
        topDictTracker.setEntryLocation('Encoding', [cff.encoding.format],
          output);
      } else {
        let compileEncoding: Uint8Array = this.compileEncoding(cff.encoding);
        topDictTracker.setEntryLocation('Encoding', [output.length], output);
        output.add(Array.from(compileEncoding));
      }
    }
    if (cff.charset !== null && cff.charset !== undefined && cff.topDict.hasName('charset')) {
      if (cff.charset.predefined) {
        topDictTracker.setEntryLocation('charset', [cff.charset.format],
          output);
      } else {
        let charset: Uint8Array = this.compileCharset(cff.charset);
        topDictTracker.setEntryLocation('charset', [output.length], output);
        output.add(Array.from(charset));
      }
    }

    let charStrings: Uint8Array = this.compileCharStrings(cff.charStrings);
    topDictTracker.setEntryLocation('CharStrings', [output.length], output);
    output.add(Array.from(charStrings));

    if (cff.isCIDFont) {


      topDictTracker.setEntryLocation('FDSelect', [output.length], output);
      let fdSelect: Uint8Array = this.compileFDSelect(cff.fdSelect.raw);
      output.add(Array.from(fdSelect));

      let compiledFDArray: CFFTrackers = this.compileTopDicts(cff.fdArray, output.length);
      topDictTracker.setEntryLocation('FDArray', [output.length], output);
      output.add(compiledFDArray.output);
      let fontDictTrackers: CFFOffsetTracker[] = compiledFDArray.trackers;

      this.compilePrivateDicts(cff.fdArray, fontDictTrackers, output);
    }

    this.compilePrivateDicts([cff.topDict as CFFTopDict], [topDictTracker], output);

    return Uint8Array.from(output.data);
  }

  encodeNumber(value: number): Uint8Array {

    if (Number.parseFloat(String(value)) == Number.parseInt(String(value)) && !Number.isNaN(value)) {

      return this.encodeInteger(value);
    }
    else {
      return this.encodeFloat(value);
    }
  }

  encodeFloat(value: number):Uint8Array {
    let valueString:string = value.toString();

    if (valueString.startsWith('0.')) {
      valueString = valueString.substring(1);
    }
    else if (valueString.startsWith('-0.')) {
      valueString = '-' + valueString.substring(Number_2);
    }

    let nibbles: number[] = new Array<number>();
    for (let i = 0; i < valueString.length; ++i) {
      let a:string = valueString.charAt(i);
      let b:string = valueString.charAt(i + 1);
      let nibble: number;
      if (a === 'e' && b === '-') {
        nibble = Number_0xc;
      } else if (a === '.') {
        nibble = Number_0xa;
      } else if (a === 'E') {
        nibble = Number_0xb;
      } else if (a === '-') {
        nibble = Number_0xe;
      } else {
        nibble = Number(a)??0;
      }
      nibbles.push(nibble);
    }
    nibbles.push(Number_0xf);

    if (nibbles.length % Number_2) {
      nibbles.push(Number_0xf);
    }

    let out: number[] = [Number_30];
    for (let i = 0; i < nibbles.length; i += Number_2){
      out.push(nibbles[i] << Number_4 | nibbles[i + 1]);
    }

    return new Uint8Array(out);
  }

  encodeInteger(value: number): Uint8Array {
    let code: number[];

    if (value >= -Number_107 && value <= Number_107) {
      code = [value + Number_139];
    } else if (value >= Number_108 && value <= Number_1131) {
      value = value -Number_108;
      code = [(value >> Number_8) + Number_247, value & Number_0xFF];
    } else if (value >= -Number_1131 && value <= -Number_108) {
      value = -value -Number_108;
      code = [(value >> Number_8) + Number_251, value & Number_0xFF];
    } else if (value >= -Number_32768 && value <= Number_32767) {
      code = [Number_0x1c, (value >> Number_8) & Number_0xFF, value & Number_0xFF];
    } else {
      code = [Number_0x1d, (value >> Number_24) & Number_0xFF, (value >> Number_16) & Number_0xFF, (value >> Number_8) & Number_0xFF, value & Number_0xFF];
    }

    return new Uint8Array(code);
  }

  compileHeader(header: CFFHeader): number[] {
    return [
      header.major, header.minor, header.hdrSize, header.offSize
    ];
  }

  compileNameIndex(names: string[]): Uint8Array {
    let nameIndex: CFFIndex = new CFFIndex();
    for (let i = 0; i < names.length; ++i){
      nameIndex.add(this.stringToArray(names[i]));
    }
    return this.compileIndex(nameIndex);
  }

  compileTopDicts(dicts: CFFDict[], length: number):CFFTrackers{
    let fontDictTrackers: CFFOffsetTracker[] = new Array<CFFOffsetTracker>();
    let fdArrayIndex: CFFIndex = new CFFIndex();

    for (let i = 0; i < dicts.length; ++i) {
      let fontDict: CFFDict = dicts[i];
      let fontDictTracker: CFFOffsetTracker = new CFFOffsetTracker();
      let fontDictData: number[] = this.compileDict(fontDict, fontDictTracker);
      fontDictTrackers.push(fontDictTracker);
      fdArrayIndex.add(new Uint8Array(fontDictData));
      fontDictTracker.offset(length);
    }

    let fdArrayIndexResult:Uint8Array = this.compileIndex(fdArrayIndex, fontDictTrackers);
    return new CFFTrackers(fontDictTrackers,fdArrayIndexResult);
  }

  compilePrivateDicts(dicts: CFFTopDict[], trackers: CFFOffsetTracker[], output: CFFOutput) {
    for (let i = 0; i < dicts.length; ++i) {
      let fontDict: CFFTopDict = dicts[i];
      if ((fontDict.privateDict === null || fontDict.privateDict === undefined) || !fontDict.hasName('Private')) {
        continue
      }

      let privateDict: CFFPrivateDict = fontDict.privateDict;
      let privateDictTracker: CFFOffsetTracker = new CFFOffsetTracker();
      let privateDictData: number[] = this.compileDict(privateDict, privateDictTracker);

      privateDictTracker.offset(output.length);
      trackers[i].setEntryLocation('Private',[privateDictData.length, output.length],output);
      output.add(privateDictData);

      if (privateDict.subrsIndex && privateDict.hasName('Subrs')) {
        let subrs: number[] = Array.from(this.compileIndex(privateDict.subrsIndex));
        privateDictTracker.setEntryLocation('Subrs', [privateDictData.length],
          output);
        output.add(subrs);
      }
    }
  }

  compileDict(dict: CFFDict, offsetTracker: CFFOffsetTracker): number[] {
    let out: number[] = new Array<number>();
    let order: number[] = dict.order;

    for (let i = 0; i < order.length; ++i) {
      let key: number = order[i];
      if (!(dict.values.has(key))) {
        continue;
      }

      let values:number | number[]= dict.values.get(key);
      let types: string | string[] = dict.types.get(key);


      if (!isArray(types)) {
        types = [types as string] ;
      }

      if (!isArray(values)) {
        values = [values as number];
      }

      if ((values as number[]).length === 0) {
        continue
      }

      for (let j = 0; j < types.length; ++j) {
        let type: string = types[j];
        let value: number = values[j];

        switch (type) {
          case 'num':
          case 'sid':
            out.concat(Array.from(this.encodeNumber(value)));
            break;
          case 'offset':
            let name: string = dict.keyToNameMap.get(key) ?? "";
            if (!offsetTracker.isTracking(name)) {
              offsetTracker.track(name, out.length);
	          }
            out.concat([0x1d, 0, 0, 0, 0]);
            break;
          case 'array':
          case 'delta':
            out.concat(Array.from(this.encodeNumber(value)));
            for (let k: number = 1; k < (values as number[]).length; ++k){
              out.concat(Array.from(this.encodeNumber(values[k])));
            }
            break;
          default:
            error(`Unknown data type of ${type}`);
            break;
        }
      }
      if (dict.opcodes.get(key) !== null && dict.opcodes.get(key) !== undefined){
        out.concat(dict.opcodes.get(key));
	    }
    }
    return out;
  }

  compileStringIndex(strings: string[]): number[] {
    let stringIndex: CFFIndex = new CFFIndex();
    for (let i = 0; i < strings.length; ++i) {
      stringIndex.add(this.stringToArray(strings[i]));
    }
    return Array.from(this.compileIndex(stringIndex));
  }

  compileGlobalSubrIndex() {
    let globalSubrIndex: CFFIndex = this.cff.globalSubrIndex;
    this.out.writeByteArray(this.compileIndex(globalSubrIndex));
  }

  compileCharStrings(charStrings: CFFIndex): Uint8Array {
    return this.compileIndex(charStrings);
  }


  compileCharset(charset: CFFCharset): Uint8Array {
    return this.compileTypedArray(charset.raw);
  }

  compileEncoding(encoding: CFFEncoding): Uint8Array {
    return this.compileTypedArray(encoding.raw);
  }

  compileFDSelect(fdSelect: Uint8Array): Uint8Array {
    return this.compileTypedArray(fdSelect);
  }

  compileTypedArray(data: Uint8Array): Uint8Array {
    let out: Uint8Array = new Uint8Array();
    for (let i = 0; i < data.length; ++i) {
      out[i] = data[i];
    }
    return out;
  }

  compileIndex(index?: CFFIndex, trackers?: CFFOffsetTracker[]): Uint8Array {
    trackers = trackers || new Array<CFFOffsetTracker>();
    let objects: Uint8Array[] = index.objects;

    let count: number = objects.length;

    if (count == 0) {
      return new Uint8Array(Number_3);
    }
    let data: number[] = [(count >> Number_8) & Number_0xFF, count & Number_0xff];

    let lastOffset: number = 1;
    for (let i = 0; i < count; ++i) {
      lastOffset += objects[i].length;
    }

    let offsetSize: number;
    if (lastOffset < Number_0x100){
      offsetSize = 1;
    }else if (lastOffset < Number_0x10000){
      offsetSize = Number_2;
    }else if (lastOffset < Number_0x1000000){
      offsetSize = Number_3;
    }else{
      offsetSize = Number_4;
    }

    data.push(offsetSize);

    let relativeOffset: number = 1;
    for (let i = 0; i < count + 1; i++) {
      if (offsetSize === 1) {
        data.push(relativeOffset & Number_0xFF);
      } else if (offsetSize === Number_2) {
        data.push((relativeOffset >> Number_8) & Number_0xFF, relativeOffset & Number_0xFF);
      } else if (offsetSize === Number_3) {
        data.push((relativeOffset >> Number_16) & Number_0xFF, (relativeOffset >> Number_8) & Number_0xFF,
          relativeOffset & Number_0xFF);
      } else {
        data.push((relativeOffset >>> Number_24) & Number_0xFF, (relativeOffset >> Number_16) & Number_0xFF,
          (relativeOffset >> Number_8) & Number_0xFF, relativeOffset & Number_0xFF);
      }

      if (objects[i]) {
        relativeOffset += objects[i].length;
      }
    }

    let offset: number = data.length;

    for (let i = 0; i < count; i++) {
      if (trackers[i]) {
        trackers[i].offset(data.length);
      }
      for (let j = 0; j < objects[i].length; j++) {
        data.push(objects[i][j]);
      }
    }
    return new Uint8Array(data);
  }
}

let GlyphsUnicode: Record<string, number> = {
  'A': 0x0041,
  'AE': 0x00C6,
  'AEacute': 0x01FC,
  'AEmacron': 0x01E2,
  'AEsmall': 0xF7E6,
  'Aacute': 0x00C1,
  'Aacutesmall': 0xF7E1,
  'Abreve': 0x0102,
  'Abreveacute': 0x1EAE,
  'Abrevecyrillic': 0x04D0,
  'Abrevedotbelow': 0x1EB6,
  'Abrevegrave': 0x1EB0,
  'Abrevehookabove': 0x1EB2,
  'Abrevetilde': 0x1EB4,
  'Acaron': 0x01CD,
  'Acircle': 0x24B6,
  'Acircumflex': 0x00C2,
  'Acircumflexacute': 0x1EA4,
  'Acircumflexdotbelow': 0x1EAC,
  'Acircumflexgrave': 0x1EA6,
  'Acircumflexhookabove': 0x1EA8,
  'Acircumflexsmall': 0xF7E2,
  'Acircumflextilde': 0x1EAA,
  'Acute': 0xF6C9,
  'Acutesmall': 0xF7B4,
  'Acyrillic': 0x0410,
  'Adblgrave': 0x0200,
  'Adieresis': 0x00C4,
  'Adieresiscyrillic': 0x04D2,
  'Adieresismacron': 0x01DE,
  'Adieresissmall': 0xF7E4,
  'Adotbelow': 0x1EA0,
  'Adotmacron': 0x01E0,
  'Agrave': 0x00C0,
  'Agravesmall': 0xF7E0,
  'Ahookabove': 0x1EA2,
  'Aiecyrillic': 0x04D4,
  'Ainvertedbreve': 0x0202,
  'Alpha': 0x0391,
  'Alphatonos': 0x0386,
  'Amacron': 0x0100,
  'Amonospace': 0xFF21,
  'Aogonek': 0x0104,
  'Aring': 0x00C5,
  'Aringacute': 0x01FA,
  'Aringbelow': 0x1E00,
  'Aringsmall': 0xF7E5,
  'Asmall': 0xF761,
  'Atilde': 0x00C3,
  'Atildesmall': 0xF7E3,
  'Aybarmenian': 0x0531,
  'B': 0x0042,
  'Bcircle': 0x24B7,
  'Bdotaccent': 0x1E02,
  'Bdotbelow': 0x1E04,
  'Becyrillic': 0x0411,
  'Benarmenian': 0x0532,
  'Beta': 0x0392,
  'Bhook': 0x0181,
  'Blinebelow': 0x1E06,
  'Bmonospace': 0xFF22,
  'Brevesmall': 0xF6F4,
  'Bsmall': 0xF762,
  'Btopbar': 0x0182,
  'C': 0x0043,
  'Caarmenian': 0x053E,
  'Cacute': 0x0106,
  'Caron': 0xF6CA,
  'Caronsmall': 0xF6F5,
  'Ccaron': 0x010C,
  'Ccedilla': 0x00C7,
  'Ccedillaacute': 0x1E08,
  'Ccedillasmall': 0xF7E7,
  'Ccircle': 0x24B8,
  'Ccircumflex': 0x0108,
  'Cdot': 0x010A,
  'Cdotaccent': 0x010A,
  'Cedillasmall': 0xF7B8,
  'Chaarmenian': 0x0549,
  'Cheabkhasiancyrillic': 0x04BC,
  'Checyrillic': 0x0427,
  'Chedescenderabkhasiancyrillic': 0x04BE,
  'Chedescendercyrillic': 0x04B6,
  'Chedieresiscyrillic': 0x04F4,
  'Cheharmenian': 0x0543,
  'Chekhakassiancyrillic': 0x04CB,
  'Cheverticalstrokecyrillic': 0x04B8,
  'Chi': 0x03A7,
  'Chook': 0x0187,
  'Circumflexsmall': 0xF6F6,
  'Cmonospace': 0xFF23,
  'Coarmenian': 0x0551,
  'Csmall': 0xF763,
  'D': 0x0044,
  'DZ': 0x01F1,
  'DZcaron': 0x01C4,
  'Daarmenian': 0x0534,
  'Dafrican': 0x0189,
  'Dcaron': 0x010E,
  'Dcedilla': 0x1E10,
  'Dcircle': 0x24B9,
  'Dcircumflexbelow': 0x1E12,
  'Dcroat': 0x0110,
  'Ddotaccent': 0x1E0A,
  'Ddotbelow': 0x1E0C,
  'Decyrillic': 0x0414,
  'Deicoptic': 0x03EE,
  'Delta': 0x2206,
  'Deltagreek': 0x0394,
  'Dhook': 0x018A,
  'Dieresis': 0xF6CB,
  'DieresisAcute': 0xF6CC,
  'DieresisGrave': 0xF6CD,
  'Dieresissmall': 0xF7A8,
  'Digammagreek': 0x03DC,
  'Djecyrillic': 0x0402,
  'Dlinebelow': 0x1E0E,
  'Dmonospace': 0xFF24,
  'Dotaccentsmall': 0xF6F7,
  'Dslash': 0x0110,
  'Dsmall': 0xF764,
  'Dtopbar': 0x018B,
  'Dz': 0x01F2,
  'Dzcaron': 0x01C5,
  'Dzeabkhasiancyrillic': 0x04E0,
  'Dzecyrillic': 0x0405,
  'Dzhecyrillic': 0x040F,
  'E': 0x0045,
  'Eacute': 0x00C9,
  'Eacutesmall': 0xF7E9,
  'Ebreve': 0x0114,
  'Ecaron': 0x011A,
  'Ecedillabreve': 0x1E1C,
  'Echarmenian': 0x0535,
  'Ecircle': 0x24BA,
  'Ecircumflex': 0x00CA,
  'Ecircumflexacute': 0x1EBE,
  'Ecircumflexbelow': 0x1E18,
  'Ecircumflexdotbelow': 0x1EC6,
  'Ecircumflexgrave': 0x1EC0,
  'Ecircumflexhookabove': 0x1EC2,
  'Ecircumflexsmall': 0xF7EA,
  'Ecircumflextilde': 0x1EC4,
  'Ecyrillic': 0x0404,
  'Edblgrave': 0x0204,
  'Edieresis': 0x00CB,
  'Edieresissmall': 0xF7EB,
  'Edot': 0x0116,
  'Edotaccent': 0x0116,
  'Edotbelow': 0x1EB8,
  'Efcyrillic': 0x0424,
  'Egrave': 0x00C8,
  'Egravesmall': 0xF7E8,
  'Eharmenian': 0x0537,
  'Ehookabove': 0x1EBA,
  'Eightroman': 0x2167,
  'Einvertedbreve': 0x0206,
  'Eiotifiedcyrillic': 0x0464,
  'Elcyrillic': 0x041B,
  'Elevenroman': 0x216A,
  'Emacron': 0x0112,
  'Emacronacute': 0x1E16,
  'Emacrongrave': 0x1E14,
  'Emcyrillic': 0x041C,
  'Emonospace': 0xFF25,
  'Encyrillic': 0x041D,
  'Endescendercyrillic': 0x04A2,
  'Eng': 0x014A,
  'Enghecyrillic': 0x04A4,
  'Enhookcyrillic': 0x04C7,
  'Eogonek': 0x0118,
  'Eopen': 0x0190,
  'Epsilon': 0x0395,
  'Epsilontonos': 0x0388,
  'Ercyrillic': 0x0420,
  'Ereversed': 0x018E,
  'Ereversedcyrillic': 0x042D,
  'Escyrillic': 0x0421,
  'Esdescendercyrillic': 0x04AA,
  'Esh': 0x01A9,
  'Esmall': 0xF765,
  'Eta': 0x0397,
  'Etarmenian': 0x0538,
  'Etatonos': 0x0389,
  'Eth': 0x00D0,
  'Ethsmall': 0xF7F0,
  'Etilde': 0x1EBC,
  'Etildebelow': 0x1E1A,
  'Euro': 0x20AC,
  'Ezh': 0x01B7,
  'Ezhcaron': 0x01EE,
  'Ezhreversed': 0x01B8,
  'F': 0x0046,
  'Fcircle': 0x24BB,
  'Fdotaccent': 0x1E1E,
  'Feharmenian': 0x0556,
  'Feicoptic': 0x03E4,
  'Fhook': 0x0191,
  'Fitacyrillic': 0x0472,
  'Fiveroman': 0x2164,
  'Fmonospace': 0xFF26,
  'Fourroman': 0x2163,
  'Fsmall': 0xF766,
  'G': 0x0047,
  'GBsquare': 0x3387,
  'Gacute': 0x01F4,
  'Gamma': 0x0393,
  'Gammaafrican': 0x0194,
  'Gangiacoptic': 0x03EA,
  'Gbreve': 0x011E,
  'Gcaron': 0x01E6,
  'Gcedilla': 0x0122,
  'Gcircle': 0x24BC,
  'Gcircumflex': 0x011C,
  'Gcommaaccent': 0x0122,
  'Gdot': 0x0120,
  'Gdotaccent': 0x0120,
  'Gecyrillic': 0x0413,
  'Ghadarmenian': 0x0542,
  'Ghemiddlehookcyrillic': 0x0494,
  'Ghestrokecyrillic': 0x0492,
  'Gheupturncyrillic': 0x0490,
  'Ghook': 0x0193,
  'Gimarmenian': 0x0533,
  'Gjecyrillic': 0x0403,
  'Gmacron': 0x1E20,
  'Gmonospace': 0xFF27,
  'Grave': 0xF6CE,
  'Gravesmall': 0xF760,
  'Gsmall': 0xF767,
  'Gsmallhook': 0x029B,
  'Gstroke': 0x01E4,
  'H': 0x0048,
  'H18533': 0x25CF,
  'H18543': 0x25AA,
  'H18551': 0x25AB,
  'H22073': 0x25A1,
  'HPsquare': 0x33CB,
  'Haabkhasiancyrillic': 0x04A8,
  'Hadescendercyrillic': 0x04B2,
  'Hardsigncyrillic': 0x042A,
  'Hbar': 0x0126,
  'Hbrevebelow': 0x1E2A,
  'Hcedilla': 0x1E28,
  'Hcircle': 0x24BD,
  'Hcircumflex': 0x0124,
  'Hdieresis': 0x1E26,
  'Hdotaccent': 0x1E22,
  'Hdotbelow': 0x1E24,
  'Hmonospace': 0xFF28,
  'Hoarmenian': 0x0540,
  'Horicoptic': 0x03E8,
  'Hsmall': 0xF768,
  'Hungarumlaut': 0xF6CF,
  'Hungarumlautsmall': 0xF6F8,
  'Hzsquare': 0x3390,
  'I': 0x0049,
  'IAcyrillic': 0x042F,
  'IJ': 0x0132,
  'IUcyrillic': 0x042E,
  'Iacute': 0x00CD,
  'Iacutesmall': 0xF7ED,
  'Ibreve': 0x012C,
  'Icaron': 0x01CF,
  'Icircle': 0x24BE,
  'Icircumflex': 0x00CE,
  'Icircumflexsmall': 0xF7EE,
  'Icyrillic': 0x0406,
  'Idblgrave': 0x0208,
  'Idieresis': 0x00CF,
  'Idieresisacute': 0x1E2E,
  'Idieresiscyrillic': 0x04E4,
  'Idieresissmall': 0xF7EF,
  'Idot': 0x0130,
  'Idotaccent': 0x0130,
  'Idotbelow': 0x1ECA,
  'Iebrevecyrillic': 0x04D6,
  'Iecyrillic': 0x0415,
  'Ifraktur': 0x2111,
  'Igrave': 0x00CC,
  'Igravesmall': 0xF7EC,
  'Ihookabove': 0x1EC8,
  'Iicyrillic': 0x0418,
  'Iinvertedbreve': 0x020A,
  'Iishortcyrillic': 0x0419,
  'Imacron': 0x012A,
  'Imacroncyrillic': 0x04E2,
  'Imonospace': 0xFF29,
  'Iniarmenian': 0x053B,
  'Iocyrillic': 0x0401,
  'Iogonek': 0x012E,
  'Iota': 0x0399,
  'Iotaafrican': 0x0196,
  'Iotadieresis': 0x03AA,
  'Iotatonos': 0x038A,
  'Ismall': 0xF769,
  'Istroke': 0x0197,
  'Itilde': 0x0128,
  'Itildebelow': 0x1E2C,
  'Izhitsacyrillic': 0x0474,
  'Izhitsadblgravecyrillic': 0x0476,
  'J': 0x004A,
  'Jaarmenian': 0x0541,
  'Jcircle': 0x24BF,
  'Jcircumflex': 0x0134,
  'Jecyrillic': 0x0408,
  'Jheharmenian': 0x054B,
  'Jmonospace': 0xFF2A,
  'Jsmall': 0xF76A,
  'K': 0x004B,
  'KBsquare': 0x3385,
  'KKsquare': 0x33CD,
  'Kabashkircyrillic': 0x04A0,
  'Kacute': 0x1E30,
  'Kacyrillic': 0x041A,
  'Kadescendercyrillic': 0x049A,
  'Kahookcyrillic': 0x04C3,
  'Kappa': 0x039A,
  'Kastrokecyrillic': 0x049E,
  'Kaverticalstrokecyrillic': 0x049C,
  'Kcaron': 0x01E8,
  'Kcedilla': 0x0136,
  'Kcircle': 0x24C0,
  'Kcommaaccent': 0x0136,
  'Kdotbelow': 0x1E32,
  'Keharmenian': 0x0554,
  'Kenarmenian': 0x053F,
  'Khacyrillic': 0x0425,
  'Kheicoptic': 0x03E6,
  'Khook': 0x0198,
  'Kjecyrillic': 0x040C,
  'Klinebelow': 0x1E34,
  'Kmonospace': 0xFF2B,
  'Koppacyrillic': 0x0480,
  'Koppagreek': 0x03DE,
  'Ksicyrillic': 0x046E,
  'Ksmall': 0xF76B,
  'L': 0x004C,
  'LJ': 0x01C7,
  'LL': 0xF6BF,
  'Lacute': 0x0139,
  'Lambda': 0x039B,
  'Lcaron': 0x013D,
  'Lcedilla': 0x013B,
  'Lcircle': 0x24C1,
  'Lcircumflexbelow': 0x1E3C,
  'Lcommaaccent': 0x013B,
  'Ldot': 0x013F,
  'Ldotaccent': 0x013F,
  'Ldotbelow': 0x1E36,
  'Ldotbelowmacron': 0x1E38,
  'Liwnarmenian': 0x053C,
  'Lj': 0x01C8,
  'Ljecyrillic': 0x0409,
  'Llinebelow': 0x1E3A,
  'Lmonospace': 0xFF2C,
  'Lslash': 0x0141,
  'Lslashsmall': 0xF6F9,
  'Lsmall': 0xF76C,
  'M': 0x004D,
  'MBsquare': 0x3386,
  'Macron': 0xF6D0,
  'Macronsmall': 0xF7AF,
  'Macute': 0x1E3E,
  'Mcircle': 0x24C2,
  'Mdotaccent': 0x1E40,
  'Mdotbelow': 0x1E42,
  'Menarmenian': 0x0544,
  'Mmonospace': 0xFF2D,
  'Msmall': 0xF76D,
  'Mturned': 0x019C,
  'Mu': 0x039C,
  'N': 0x004E,
  'NJ': 0x01CA,
  'Nacute': 0x0143,
  'Ncaron': 0x0147,
  'Ncedilla': 0x0145,
  'Ncircle': 0x24C3,
  'Ncircumflexbelow': 0x1E4A,
  'Ncommaaccent': 0x0145,
  'Ndotaccent': 0x1E44,
  'Ndotbelow': 0x1E46,
  'Nhookleft': 0x019D,
  'Nineroman': 0x2168,
  'Nj': 0x01CB,
  'Njecyrillic': 0x040A,
  'Nlinebelow': 0x1E48,
  'Nmonospace': 0xFF2E,
  'Nowarmenian': 0x0546,
  'Nsmall': 0xF76E,
  'Ntilde': 0x00D1,
  'Ntildesmall': 0xF7F1,
  'Nu': 0x039D,
  'O': 0x004F,
  'OE': 0x0152,
  'OEsmall': 0xF6FA,
  'Oacute': 0x00D3,
  'Oacutesmall': 0xF7F3,
  'Obarredcyrillic': 0x04E8,
  'Obarreddieresiscyrillic': 0x04EA,
  'Obreve': 0x014E,
  'Ocaron': 0x01D1,
  'Ocenteredtilde': 0x019F,
  'Ocircle': 0x24C4,
  'Ocircumflex': 0x00D4,
  'Ocircumflexacute': 0x1ED0,
  'Ocircumflexdotbelow': 0x1ED8,
  'Ocircumflexgrave': 0x1ED2,
  'Ocircumflexhookabove': 0x1ED4,
  'Ocircumflexsmall': 0xF7F4,
  'Ocircumflextilde': 0x1ED6,
  'Ocyrillic': 0x041E,
  'Odblacute': 0x0150,
  'Odblgrave': 0x020C,
  'Odieresis': 0x00D6,
  'Odieresiscyrillic': 0x04E6,
  'Odieresissmall': 0xF7F6,
  'Odotbelow': 0x1ECC,
  'Ogoneksmall': 0xF6FB,
  'Ograve': 0x00D2,
  'Ogravesmall': 0xF7F2,
  'Oharmenian': 0x0555,
  'Ohm': 0x2126,
  'Ohookabove': 0x1ECE,
  'Ohorn': 0x01A0,
  'Ohornacute': 0x1EDA,
  'Ohorndotbelow': 0x1EE2,
  'Ohorngrave': 0x1EDC,
  'Ohornhookabove': 0x1EDE,
  'Ohorntilde': 0x1EE0,
  'Ohungarumlaut': 0x0150,
  'Oi': 0x01A2,
  'Oinvertedbreve': 0x020E,
  'Omacron': 0x014C,
  'Omacronacute': 0x1E52,
  'Omacrongrave': 0x1E50,
  'Omega': 0x2126,
  'Omegacyrillic': 0x0460,
  'Omegagreek': 0x03A9,
  'Omegaroundcyrillic': 0x047A,
  'Omegatitlocyrillic': 0x047C,
  'Omegatonos': 0x038F,
  'Omicron': 0x039F,
  'Omicrontonos': 0x038C,
  'Omonospace': 0xFF2F,
  'Oneroman': 0x2160,
  'Oogonek': 0x01EA,
  'Oogonekmacron': 0x01EC,
  'Oopen': 0x0186,
  'Oslash': 0x00D8,
  'Oslashacute': 0x01FE,
  'Oslashsmall': 0xF7F8,
  'Osmall': 0xF76F,
  'Ostrokeacute': 0x01FE,
  'Otcyrillic': 0x047E,
  'Otilde': 0x00D5,
  'Otildeacute': 0x1E4C,
  'Otildedieresis': 0x1E4E,
  'Otildesmall': 0xF7F5,
  'P': 0x0050,
  'Pacute': 0x1E54,
  'Pcircle': 0x24C5,
  'Pdotaccent': 0x1E56,
  'Pecyrillic': 0x041F,
  'Peharmenian': 0x054A,
  'Pemiddlehookcyrillic': 0x04A6,
  'Phi': 0x03A6,
  'Phook': 0x01A4,
  'Pi': 0x03A0,
  'Piwrarmenian': 0x0553,
  'Pmonospace': 0xFF30,
  'Psi': 0x03A8,
  'Psicyrillic': 0x0470,
  'Psmall': 0xF770,
  'Q': 0x0051,
  'Qcircle': 0x24C6,
  'Qmonospace': 0xFF31,
  'Qsmall': 0xF771,
  'R': 0x0052,
  'Raarmenian': 0x054C,
  'Racute': 0x0154,
  'Rcaron': 0x0158,
  'Rcedilla': 0x0156,
  'Rcircle': 0x24C7,
  'Rcommaaccent': 0x0156,
  'Rdblgrave': 0x0210,
  'Rdotaccent': 0x1E58,
  'Rdotbelow': 0x1E5A,
  'Rdotbelowmacron': 0x1E5C,
  'Reharmenian': 0x0550,
  'Rfraktur': 0x211C,
  'Rho': 0x03A1,
  'Ringsmall': 0xF6FC,
  'Rinvertedbreve': 0x0212,
  'Rlinebelow': 0x1E5E,
  'Rmonospace': 0xFF32,
  'Rsmall': 0xF772,
  'Rsmallinverted': 0x0281,
  'Rsmallinvertedsuperior': 0x02B6,
  'S': 0x0053,
  'SF010000': 0x250C,
  'SF020000': 0x2514,
  'SF030000': 0x2510,
  'SF040000': 0x2518,
  'SF050000': 0x253C,
  'SF060000': 0x252C,
  'SF070000': 0x2534,
  'SF080000': 0x251C,
  'SF090000': 0x2524,
  'SF100000': 0x2500,
  'SF110000': 0x2502,
  'SF190000': 0x2561,
  'SF200000': 0x2562,
  'SF210000': 0x2556,
  'SF220000': 0x2555,
  'SF230000': 0x2563,
  'SF240000': 0x2551,
  'SF250000': 0x2557,
  'SF260000': 0x255D,
  'SF270000': 0x255C,
  'SF280000': 0x255B,
  'SF360000': 0x255E,
  'SF370000': 0x255F,
  'SF380000': 0x255A,
  'SF390000': 0x2554,
  'SF400000': 0x2569,
  'SF410000': 0x2566,
  'SF420000': 0x2560,
  'SF430000': 0x2550,
  'SF440000': 0x256C,
  'SF450000': 0x2567,
  'SF460000': 0x2568,
  'SF470000': 0x2564,
  'SF480000': 0x2565,
  'SF490000': 0x2559,
  'SF500000': 0x2558,
  'SF510000': 0x2552,
  'SF520000': 0x2553,
  'SF530000': 0x256B,
  'SF540000': 0x256A,
  'Sacute': 0x015A,
  'Sacutedotaccent': 0x1E64,
  'Sampigreek': 0x03E0,
  'Scaron': 0x0160,
  'Scarondotaccent': 0x1E66,
  'Scaronsmall': 0xF6FD,
  'Scedilla': 0x015E,
  'Schwa': 0x018F,
  'Schwacyrillic': 0x04D8,
  'Schwadieresiscyrillic': 0x04DA,
  'Scircle': 0x24C8,
  'Scircumflex': 0x015C,
  'Scommaaccent': 0x0218,
  'Sdotaccent': 0x1E60,
  'Sdotbelow': 0x1E62,
  'Sdotbelowdotaccent': 0x1E68,
  'Seharmenian': 0x054D,
  'Sevenroman': 0x2166,
  'Shaarmenian': 0x0547,
  'Shacyrillic': 0x0428,
  'Shchacyrillic': 0x0429,
  'Sheicoptic': 0x03E2,
  'Shhacyrillic': 0x04BA,
  'Shimacoptic': 0x03EC,
  'Sigma': 0x03A3,
  'Sixroman': 0x2165,
  'Smonospace': 0xFF33,
  'Softsigncyrillic': 0x042C,
  'Ssmall': 0xF773,
  'Stigmagreek': 0x03DA,
  'T': 0x0054,
  'Tau': 0x03A4,
  'Tbar': 0x0166,
  'Tcaron': 0x0164,
  'Tcedilla': 0x0162,
  'Tcircle': 0x24C9,
  'Tcircumflexbelow': 0x1E70,
  'Tcommaaccent': 0x0162,
  'Tdotaccent': 0x1E6A,
  'Tdotbelow': 0x1E6C,
  'Tecyrillic': 0x0422,
  'Tedescendercyrillic': 0x04AC,
  'Tenroman': 0x2169,
  'Tetsecyrillic': 0x04B4,
  'Theta': 0x0398,
  'Thook': 0x01AC,
  'Thorn': 0x00DE,
  'Thornsmall': 0xF7FE,
  'Threeroman': 0x2162,
  'Tildesmall': 0xF6FE,
  'Tiwnarmenian': 0x054F,
  'Tlinebelow': 0x1E6E,
  'Tmonospace': 0xFF34,
  'Toarmenian': 0x0539,
  'Tonefive': 0x01BC,
  'Tonesix': 0x0184,
  'Tonetwo': 0x01A7,
  'Tretroflexhook': 0x01AE,
  'Tsecyrillic': 0x0426,
  'Tshecyrillic': 0x040B,
  'Tsmall': 0xF774,
  'Twelveroman': 0x216B,
  'Tworoman': 0x2161,
  'U': 0x0055,
  'Uacute': 0x00DA,
  'Uacutesmall': 0xF7FA,
  'Ubreve': 0x016C,
  'Ucaron': 0x01D3,
  'Ucircle': 0x24CA,
  'Ucircumflex': 0x00DB,
  'Ucircumflexbelow': 0x1E76,
  'Ucircumflexsmall': 0xF7FB,
  'Ucyrillic': 0x0423,
  'Udblacute': 0x0170,
  'Udblgrave': 0x0214,
  'Udieresis': 0x00DC,
  'Udieresisacute': 0x01D7,
  'Udieresisbelow': 0x1E72,
  'Udieresiscaron': 0x01D9,
  'Udieresiscyrillic': 0x04F0,
  'Udieresisgrave': 0x01DB,
  'Udieresismacron': 0x01D5,
  'Udieresissmall': 0xF7FC,
  'Udotbelow': 0x1EE4,
  'Ugrave': 0x00D9,
  'Ugravesmall': 0xF7F9,
  'Uhookabove': 0x1EE6,
  'Uhorn': 0x01AF,
  'Uhornacute': 0x1EE8,
  'Uhorndotbelow': 0x1EF0,
  'Uhorngrave': 0x1EEA,
  'Uhornhookabove': 0x1EEC,
  'Uhorntilde': 0x1EEE,
  'Uhungarumlaut': 0x0170,
  'Uhungarumlautcyrillic': 0x04F2,
  'Uinvertedbreve': 0x0216,
  'Ukcyrillic': 0x0478,
  'Umacron': 0x016A,
  'Umacroncyrillic': 0x04EE,
  'Umacrondieresis': 0x1E7A,
  'Umonospace': 0xFF35,
  'Uogonek': 0x0172,
  'Upsilon': 0x03A5,
  'Upsilon1': 0x03D2,
  'Upsilonacutehooksymbolgreek': 0x03D3,
  'Upsilonafrican': 0x01B1,
  'Upsilondieresis': 0x03AB,
  'Upsilondieresishooksymbolgreek': 0x03D4,
  'Upsilonhooksymbol': 0x03D2,
  'Upsilontonos': 0x038E,
  'Uring': 0x016E,
  'Ushortcyrillic': 0x040E,
  'Usmall': 0xF775,
  'Ustraightcyrillic': 0x04AE,
  'Ustraightstrokecyrillic': 0x04B0,
  'Utilde': 0x0168,
  'Utildeacute': 0x1E78,
  'Utildebelow': 0x1E74,
  'V': 0x0056,
  'Vcircle': 0x24CB,
  'Vdotbelow': 0x1E7E,
  'Vecyrillic': 0x0412,
  'Vewarmenian': 0x054E,
  'Vhook': 0x01B2,
  'Vmonospace': 0xFF36,
  'Voarmenian': 0x0548,
  'Vsmall': 0xF776,
  'Vtilde': 0x1E7C,
  'W': 0x0057,
  'Wacute': 0x1E82,
  'Wcircle': 0x24CC,
  'Wcircumflex': 0x0174,
  'Wdieresis': 0x1E84,
  'Wdotaccent': 0x1E86,
  'Wdotbelow': 0x1E88,
  'Wgrave': 0x1E80,
  'Wmonospace': 0xFF37,
  'Wsmall': 0xF777,
  'X': 0x0058,
  'Xcircle': 0x24CD,
  'Xdieresis': 0x1E8C,
  'Xdotaccent': 0x1E8A,
  'Xeharmenian': 0x053D,
  'Xi': 0x039E,
  'Xmonospace': 0xFF38,
  'Xsmall': 0xF778,
  'Y': 0x0059,
  'Yacute': 0x00DD,
  'Yacutesmall': 0xF7FD,
  'Yatcyrillic': 0x0462,
  'Ycircle': 0x24CE,
  'Ycircumflex': 0x0176,
  'Ydieresis': 0x0178,
  'Ydieresissmall': 0xF7FF,
  'Ydotaccent': 0x1E8E,
  'Ydotbelow': 0x1EF4,
  'Yericyrillic': 0x042B,
  'Yerudieresiscyrillic': 0x04F8,
  'Ygrave': 0x1EF2,
  'Yhook': 0x01B3,
  'Yhookabove': 0x1EF6,
  'Yiarmenian': 0x0545,
  'Yicyrillic': 0x0407,
  'Yiwnarmenian': 0x0552,
  'Ymonospace': 0xFF39,
  'Ysmall': 0xF779,
  'Ytilde': 0x1EF8,
  'Yusbigcyrillic': 0x046A,
  'Yusbigiotifiedcyrillic': 0x046C,
  'Yuslittlecyrillic': 0x0466,
  'Yuslittleiotifiedcyrillic': 0x0468,
  'Z': 0x005A,
  'Zaarmenian': 0x0536,
  'Zacute': 0x0179,
  'Zcaron': 0x017D,
  'Zcaronsmall': 0xF6FF,
  'Zcircle': 0x24CF,
  'Zcircumflex': 0x1E90,
  'Zdot': 0x017B,
  'Zdotaccent': 0x017B,
  'Zdotbelow': 0x1E92,
  'Zecyrillic': 0x0417,
  'Zedescendercyrillic': 0x0498,
  'Zedieresiscyrillic': 0x04DE,
  'Zeta': 0x0396,
  'Zhearmenian': 0x053A,
  'Zhebrevecyrillic': 0x04C1,
  'Zhecyrillic': 0x0416,
  'Zhedescendercyrillic': 0x0496,
  'Zhedieresiscyrillic': 0x04DC,
  'Zlinebelow': 0x1E94,
  'Zmonospace': 0xFF3A,
  'Zsmall': 0xF77A,
  'Zstroke': 0x01B5,
  'a': 0x0061,
  'aabengali': 0x0986,
  'aacute': 0x00E1,
  'aadeva': 0x0906,
  'aagujarati': 0x0A86,
  'aagurmukhi': 0x0A06,
  'aamatragurmukhi': 0x0A3E,
  'aarusquare': 0x3303,
  'aavowelsignbengali': 0x09BE,
  'aavowelsigndeva': 0x093E,
  'aavowelsigngujarati': 0x0ABE,
  'abbreviationmarkarmenian': 0x055F,
  'abbreviationsigndeva': 0x0970,
  'abengali': 0x0985,
  'abopomofo': 0x311A,
  'abreve': 0x0103,
  'abreveacute': 0x1EAF,
  'abrevecyrillic': 0x04D1,
  'abrevedotbelow': 0x1EB7,
  'abrevegrave': 0x1EB1,
  'abrevehookabove': 0x1EB3,
  'abrevetilde': 0x1EB5,
  'acaron': 0x01CE,
  'acircle': 0x24D0,
  'acircumflex': 0x00E2,
  'acircumflexacute': 0x1EA5,
  'acircumflexdotbelow': 0x1EAD,
  'acircumflexgrave': 0x1EA7,
  'acircumflexhookabove': 0x1EA9,
  'acircumflextilde': 0x1EAB,
  'acute': 0x00B4,
  'acutebelowcmb': 0x0317,
  'acutecmb': 0x0301,
  'acutecomb': 0x0301,
  'acutedeva': 0x0954,
  'acutelowmod': 0x02CF,
  'acutetonecmb': 0x0341,
  'acyrillic': 0x0430,
  'adblgrave': 0x0201,
  'addakgurmukhi': 0x0A71,
  'adeva': 0x0905,
  'adieresis': 0x00E4,
  'adieresiscyrillic': 0x04D3,
  'adieresismacron': 0x01DF,
  'adotbelow': 0x1EA1,
  'adotmacron': 0x01E1,
  'ae': 0x00E6,
  'aeacute': 0x01FD,
  'aekorean': 0x3150,
  'aemacron': 0x01E3,
  'afii00208': 0x2015,
  'afii08941': 0x20A4,
  'afii10017': 0x0410,
  'afii10018': 0x0411,
  'afii10019': 0x0412,
  'afii10020': 0x0413,
  'afii10021': 0x0414,
  'afii10022': 0x0415,
  'afii10023': 0x0401,
  'afii10024': 0x0416,
  'afii10025': 0x0417,
  'afii10026': 0x0418,
  'afii10027': 0x0419,
  'afii10028': 0x041A,
  'afii10029': 0x041B,
  'afii10030': 0x041C,
  'afii10031': 0x041D,
  'afii10032': 0x041E,
  'afii10033': 0x041F,
  'afii10034': 0x0420,
  'afii10035': 0x0421,
  'afii10036': 0x0422,
  'afii10037': 0x0423,
  'afii10038': 0x0424,
  'afii10039': 0x0425,
  'afii10040': 0x0426,
  'afii10041': 0x0427,
  'afii10042': 0x0428,
  'afii10043': 0x0429,
  'afii10044': 0x042A,
  'afii10045': 0x042B,
  'afii10046': 0x042C,
  'afii10047': 0x042D,
  'afii10048': 0x042E,
  'afii10049': 0x042F,
  'afii10050': 0x0490,
  'afii10051': 0x0402,
  'afii10052': 0x0403,
  'afii10053': 0x0404,
  'afii10054': 0x0405,
  'afii10055': 0x0406,
  'afii10056': 0x0407,
  'afii10057': 0x0408,
  'afii10058': 0x0409,
  'afii10059': 0x040A,
  'afii10060': 0x040B,
  'afii10061': 0x040C,
  'afii10062': 0x040E,
  'afii10063': 0xF6C4,
  'afii10064': 0xF6C5,
  'afii10065': 0x0430,
  'afii10066': 0x0431,
  'afii10067': 0x0432,
  'afii10068': 0x0433,
  'afii10069': 0x0434,
  'afii10070': 0x0435,
  'afii10071': 0x0451,
  'afii10072': 0x0436,
  'afii10073': 0x0437,
  'afii10074': 0x0438,
  'afii10075': 0x0439,
  'afii10076': 0x043A,
  'afii10077': 0x043B,
  'afii10078': 0x043C,
  'afii10079': 0x043D,
  ' afii10080': 0x043E,
  ' afii10081': 0x043F,
  ' afii10082': 0x0440,
  ' afii10083': 0x0441,
  ' afii10084': 0x0442,
  ' afii10085': 0x0443,
  ' afii10086': 0x0444,
  ' afii10087': 0x0445,
  ' afii10088': 0x0446,
  ' afii10089': 0x0447,
  ' afii10090': 0x0448,
  ' afii10091': 0x0449,
  ' afii10092': 0x044A,
  ' afii10093': 0x044B,
  ' afii10094': 0x044C,
  ' afii10095': 0x044D,
  ' afii10096': 0x044E,
  ' afii10097': 0x044F,
  ' afii10098': 0x0491,
  ' afii10099': 0x0452,
  ' afii10100': 0x0453,
  ' afii10101': 0x0454,
  ' afii10102': 0x0455,
  ' afii10103': 0x0456,
  ' afii10104': 0x0457,
  ' afii10105': 0x0458,
  ' afii10106': 0x0459,
  ' afii10107': 0x045A,
  ' afii10108': 0x045B,
  ' afii10109': 0x045C,
  ' afii10110': 0x045E,
  ' afii10145': 0x040F,
  'afii10146': 0x0462,
  'afii10147': 0x0472,
  'afii10148': 0x0474,
  'afii10192': 0xF6C6,
  'afii10193': 0x045F,
  'afii10194': 0x0463,
  'afii10195': 0x0473,
  'afii10196': 0x0475,
  'afii10831': 0xF6C7,
  'afii10832': 0xF6C8,
  'afii10846': 0x04D9,
  'afii299': 0x200E,
  'afii300': 0x200F,
  'afii301': 0x200D,
  'afii57381': 0x066A,
  'afii57388': 0x060C,
  'afii57392': 0x0660,
  'afii57393': 0x0661,
  'afii57394': 0x0662,
  'afii57395': 0x0663,
  'afii57396': 0x0664,
  'afii57397': 0x0665,
  'afii57398': 0x0666,
  'afii57399': 0x0667,
  'afii57400': 0x0668,
  'afii57401': 0x0669,
  'afii57403': 0x061B,
  'afii57407': 0x061F,
  'afii57409': 0x0621,
  'afii57410': 0x0622,
  'afii57411': 0x0623,
  'afii57412': 0x0624,
  'afii57413': 0x0625,
  'afii57414': 0x0626,
  'afii57415': 0x0627,
  'afii57416': 0x0628,
  'afii57417': 0x0629,
  'afii57418': 0x062A,
  'afii57419': 0x062B,
  'afii57420': 0x062C,
  'afii57421': 0x062D,
  'afii57422': 0x062E,
  'afii57423': 0x062F,
  'afii57424': 0x0630,
  'afii57425': 0x0631,
  'afii57426': 0x0632,
  'afii57427': 0x0633,
  'afii57428': 0x0634,
  'afii57429': 0x0635,
  'afii57430': 0x0636,
  'afii57431': 0x0637,
  'afii57432': 0x0638,
  'afii57433': 0x0639,
  'afii57434': 0x063A,
  'afii57440': 0x0640,
  'afii57441': 0x0641,
  'afii57442': 0x0642,
  'afii57443': 0x0643,
  'afii57444': 0x0644,
  'afii57445': 0x0645,
  'afii57446': 0x0646,
  'afii57448': 0x0648,
  'afii57449': 0x0649,
  'afii57450': 0x064A,
  'afii57451': 0x064B,
  'afii57452': 0x064C,
  'afii57453': 0x064D,
  'afii57454': 0x064E,
  'afii57455': 0x064F,
  'afii57456': 0x0650,
  'afii57457': 0x0651,
  'afii57458': 0x0652,
  'afii57470': 0x0647,
  'afii57505': 0x06A4,
  'afii57506': 0x067E,
  'afii57507': 0x0686,
  'afii57508': 0x0698,
  'afii57509': 0x06AF,
  'afii57511': 0x0679,
  'afii57512': 0x0688,
  'afii57513': 0x0691,
  'afii57514': 0x06BA,
  'afii57519': 0x06D2,
  'afii57534': 0x06D5,
  'afii57636': 0x20AA,
  'afii57645': 0x05BE,
  'afii57658': 0x05C3,
  'afii57664': 0x05D0,
  'afii57665': 0x05D1,
  'afii57666': 0x05D2,
  'afii57667': 0x05D3,
  'afii57668': 0x05D4,
  'afii57669': 0x05D5,
  'afii57670': 0x05D6,
  'afii57671': 0x05D7,
  'afii57672': 0x05D8,
  'afii57673': 0x05D9,
  'afii57674': 0x05DA,
  'afii57675': 0x05DB,
  'afii57676': 0x05DC,
  'afii57677': 0x05DD,
  'afii57678': 0x05DE,
  'afii57679': 0x05DF,
  'afii57680': 0x05E0,
  'afii57681': 0x05E1,
  'afii57682': 0x05E2,
  'afii57683': 0x05E3,
  'afii57684': 0x05E4,
  'afii57685': 0x05E5,
  'afii57686': 0x05E6,
  'afii57687': 0x05E7,
  'afii57688': 0x05E8,
  'afii57689': 0x05E9,
  'afii57690': 0x05EA,
  'afii57694': 0xFB2A,
  'afii57695': 0xFB2B,
  'afii57700': 0xFB4B,
  'afii57705': 0xFB1F,
  'afii57716': 0x05F0,
  'afii57717': 0x05F1,
  'afii57718': 0x05F2,
  'afii57723': 0xFB35,
  'afii57793': 0x05B4,
  'afii57794': 0x05B5,
  'afii57795': 0x05B6,
  'afii57796': 0x05BB,
  'afii57797': 0x05B8,
  'afii57798': 0x05B7,
  'afii57799': 0x05B0,
  'afii57800': 0x05B2,
  'afii57801': 0x05B1,
  'afii57802': 0x05B3,
  'afii57803': 0x05C2,
  'afii57804': 0x05C1,
  'afii57806': 0x05B9,
  'afii57807': 0x05BC,
  'afii57839': 0x05BD,
  'afii57841': 0x05BF,
  'afii57842': 0x05C0,
  'afii57929': 0x02BC,
  'afii61248': 0x2105,
  'afii61289': 0x2113,
  'afii61352': 0x2116,
  'afii61573': 0x202C,
  'afii61574': 0x202D,
  'afii61575': 0x202E,
  'afii61664': 0x200C,
  'afii63167': 0x066D,
  'afii64937': 0x02BD,
  'agrave': 0x00E0,
  'agujarati': 0x0A85,
  'agurmukhi': 0x0A05,
  'ahiragana': 0x3042,
  'ahookabove': 0x1EA3,
  'aibengali': 0x0990,
  'aibopomofo': 0x311E,
  'aideva': 0x0910,
  'aiecyrillic': 0x04D5,
  'aigujarati': 0x0A90,
  'aigurmukhi': 0x0A10,
  'aimatragurmukhi': 0x0A48,
  'ainarabic': 0x0639,
  'ainfinalarabic': 0xFECA,
  'aininitialarabic': 0xFECB,
  'ainmedialarabic': 0xFECC,
  'ainvertedbreve': 0x0203,
  'aivowelsignbengali': 0x09C8,
  'aivowelsigndeva': 0x0948,
  'aivowelsigngujarati': 0x0AC8,
  'akatakana': 0x30A2,
  'akatakanahalfwidth': 0xFF71,
  'akorean': 0x314F,
  'alef': 0x05D0,
  'alefarabic': 0x0627,
  'alefdageshhebrew': 0xFB30,
  'aleffinalarabic': 0xFE8E,
  'alefhamzaabovearabic': 0x0623,
  'alefhamzaabovefinalarabic': 0xFE84,
  'alefhamzabelowarabic': 0x0625,
  'alefhamzabelowfinalarabic': 0xFE88,
  'alefhebrew': 0x05D0,
  'aleflamedhebrew': 0xFB4F,
  'alefmaddaabovearabic': 0x0622,
  'alefmaddaabovefinalarabic': 0xFE82,
  'alefmaksuraarabic': 0x0649,
  'alefmaksurafinalarabic': 0xFEF0,
  'alefmaksurainitialarabic': 0xFEF3,
  'alefmaksuramedialarabic': 0xFEF4,
  'alefpatahhebrew': 0xFB2E,
  'alefqamatshebrew': 0xFB2F,
  'aleph': 0x2135,
  'allequal': 0x224C,
  'alpha': 0x03B1,
  'alphatonos': 0x03AC,
  'amacron': 0x0101,
  'amonospace': 0xFF41,
  'ampersand': 0x0026,
  'ampersandmonospace': 0xFF06,
  'ampersandsmall': 0xF726,
  'amsquare': 0x33C2,
  'anbopomofo': 0x3122,
  'angbopomofo': 0x3124,
  'angbracketleft': 0x3008,
  'angbracketright': 0x3009,
  'angkhankhuthai': 0x0E5A,
  'angle': 0x2220,
  'anglebracketleft': 0x3008,
  'anglebracketleftvertical': 0xFE3F,
  'anglebracketright': 0x3009,
  'anglebracketrightvertical': 0xFE40,
  'angleleft': 0x2329,
  'angleright': 0x232A,
  'angstrom': 0x212B,
  'anoteleia': 0x0387,
  'anudattadeva': 0x0952,
  'anusvarabengali': 0x0982,
  'anusvaradeva': 0x0902,
  'anusvaragujarati': 0x0A82,
  'aogonek': 0x0105,
  'apaatosquare': 0x3300,
  'aparen': 0x249C,
  'apostrophearmenian': 0x055A,
  'apostrophemod': 0x02BC,
  'apple': 0xF8FF,
  'approaches': 0x2250,
  'approxequal': 0x2248,
  'approxequalorimage': 0x2252,
  'approximatelyequal': 0x2245,
  'araeaekorean': 0x318E,
  'araeakorean': 0x318D,
  'arc': 0x2312,
  'arighthalfring': 0x1E9A,
  'aring': 0x00E5,
  'aringacute': 0x01FB,
  'aringbelow': 0x1E01,
  'arrowboth': 0x2194,
  'arrowdashdown': 0x21E3,
  'arrowdashleft': 0x21E0,
  'arrowdashright': 0x21E2,
  'arrowdashup': 0x21E1,
  'arrowdblboth': 0x21D4,
  'arrowdbldown': 0x21D3,
  'arrowdblleft': 0x21D0,
  'arrowdblright': 0x21D2,
  'arrowdblup': 0x21D1,
  'arrowdown': 0x2193,
  'arrowdownleft': 0x2199,
  'arrowdownright': 0x2198,
  'arrowdownwhite': 0x21E9,
  'arrowheaddownmod': 0x02C5,
  'arrowheadleftmod': 0x02C2,
  'arrowheadrightmod': 0x02C3,
  'arrowheadupmod': 0x02C4,
  'arrowhorizex': 0xF8E7,
  'arrowleft': 0x2190,
  'arrowleftdbl': 0x21D0,
  'arrowleftdblstroke': 0x21CD,
  'arrowleftoverright': 0x21C6,
  'arrowleftwhite': 0x21E6,
  'arrowright': 0x2192,
  'arrowrightdblstroke': 0x21CF,
  'arrowrightheavy': 0x279E,
  'arrowrightoverleft': 0x21C4,
  'arrowrightwhite': 0x21E8,
  'arrowtableft': 0x21E4,
  'arrowtabright': 0x21E5,
  'arrowup': 0x2191,
  'arrowupdn': 0x2195,
  'arrowupdnbse': 0x21A8,
  'arrowupdownbase': 0x21A8,
  'arrowupleft': 0x2196,
  'arrowupleftofdown': 0x21C5,
  'arrowupright': 0x2197,
  'arrowupwhite': 0x21E7,
  'arrowvertex': 0xF8E6,
  'asciicircum': 0x005E,
  'asciicircummonospace': 0xFF3E,
  'asciitilde': 0x007E,
  'asciitildemonospace': 0xFF5E,
  'ascript': 0x0251,
  'ascriptturned': 0x0252,
  'asmallhiragana': 0x3041,
  'asmallkatakana': 0x30A1,
  'asmallkatakanahalfwidth': 0xFF67,
  'asterisk': 0x002A,
  'asteriskaltonearabic': 0x066D,
  'asteriskarabic': 0x066D,
  'asteriskmath': 0x2217,
  'asteriskmonospace': 0xFF0A,
  'asterisksmall': 0xFE61,
  'asterism': 0x2042,
  'asuperior': 0xF6E9,
  'asymptoticallyequal': 0x2243,
  'at': 0x0040,
  'atilde': 0x00E3,
  'atmonospace': 0xFF20,
  'atsmall': 0xFE6B,
  'aturned': 0x0250,
  'aubengali': 0x0994,
  'aubopomofo': 0x3120,
  'audeva': 0x0914,
  'augujarati': 0x0A94,
  'augurmukhi': 0x0A14,
  'aulengthmarkbengali': 0x09D7,
  'aumatragurmukhi': 0x0A4C,
  'auvowelsignbengali': 0x09CC,
  'auvowelsigndeva': 0x094C,
  'auvowelsigngujarati': 0x0ACC,
  'avagrahadeva': 0x093D,
  'aybarmenian': 0x0561,
  'ayin': 0x05E2,
  'ayinaltonehebrew': 0xFB20,
  'ayinhebrew': 0x05E2,
  'b': 0x0062,
  'babengali': 0x09AC,
  'backslash': 0x005C,
  'backslashmonospace': 0xFF3C,
  'badeva': 0x092C,
  'bagujarati': 0x0AAC,
  'bagurmukhi': 0x0A2C,
  'bahiragana': 0x3070,
  'bahtthai': 0x0E3F,
  'bakatakana': 0x30D0,
  'bar': 0x007C,
  'barmonospace': 0xFF5C,
  'bbopomofo': 0x3105,
  'bcircle': 0x24D1,
  'bdotaccent': 0x1E03,
  'bdotbelow': 0x1E05,
  'beamedsixteenthnotes': 0x266C,
  'because': 0x2235,
  'becyrillic': 0x0431,
  'beharabic': 0x0628,
  'behfinalarabic': 0xFE90,
  'behinitialarabic': 0xFE91,
  'behiragana': 0x3079,
  'behmedialarabic': 0xFE92,
  'behmeeminitialarabic': 0xFC9F,
  'behmeemisolatedarabic': 0xFC08,
  'behnoonfinalarabic': 0xFC6D,
  'bekatakana': 0x30D9,
  'benarmenian': 0x0562,
  'bet': 0x05D1,
  'beta': 0x03B2,
  'betasymbolgreek': 0x03D0,
  'betdagesh': 0xFB31,
  'betdageshhebrew': 0xFB31,
  'bethebrew': 0x05D1,
  'betrafehebrew': 0xFB4C,
  'bhabengali': 0x09AD,
  'bhadeva': 0x092D,
  'bhagujarati': 0x0AAD,
  'bhagurmukhi': 0x0A2D,
  'bhook': 0x0253,
  'bihiragana': 0x3073,
  'bikatakana': 0x30D3,
  'bilabialclick': 0x0298,
  'bindigurmukhi': 0x0A02,
  'birusquare': 0x3331,
  'blackcircle': 0x25CF,
  'blackdiamond': 0x25C6,
  'blackdownpointingtriangle': 0x25BC,
  'blackleftpointingpointer': 0x25C4,
  'blackleftpointingtriangle': 0x25C0,
  'blacklenticularbracketleft': 0x3010,
  'blacklenticularbracketleftvertical': 0xFE3B,
  'blacklenticularbracketright': 0x3011,
  'blacklenticularbracketrightvertical': 0xFE3C,
  'blacklowerlefttriangle': 0x25E3,
  'blacklowerrighttriangle': 0x25E2,
  'blackrectangle': 0x25AC,
  'blackrightpointingpointer': 0x25BA,
  'blackrightpointingtriangle': 0x25B6,
  'blacksmallsquare': 0x25AA,
  'blacksmilingface': 0x263B,
  'blacksquare': 0x25A0,
  'blackstar': 0x2605,
  'blackupperlefttriangle': 0x25E4,
  'blackupperrighttriangle': 0x25E5,
  'blackuppointingsmalltriangle': 0x25B4,
  'blackuppointingtriangle': 0x25B2,
  'blank': 0x2423,
  'blinebelow': 0x1E07,
  'block': 0x2588,
  'bmonospace': 0xFF42,
  'bobaimaithai': 0x0E1A,
  'bohiragana': 0x307C,
  'bokatakana': 0x30DC,
  'bparen': 0x249D,
  'bqsquare': 0x33C3,
  'braceex': 0xF8F4,
  'braceleft': 0x007B,
  'braceleftbt': 0xF8F3,
  'braceleftmid': 0xF8F2,
  'braceleftmonospace': 0xFF5B,
  'braceleftsmall': 0xFE5B,
  'bracelefttp': 0xF8F1,
  'braceleftvertical': 0xFE37,
  'braceright': 0x007D,
  'bracerightbt': 0xF8FE,
  'bracerightmid': 0xF8FD,
  'bracerightmonospace': 0xFF5D,
  'bracerightsmall': 0xFE5C,
  'bracerighttp': 0xF8FC,
  'bracerightvertical': 0xFE38,
  'bracketleft': 0x005B,
  'bracketleftbt': 0xF8F0,
  'bracketleftex': 0xF8EF,
  'bracketleftmonospace': 0xFF3B,
  'bracketlefttp': 0xF8EE,
  'bracketright': 0x005D,
  'bracketrightbt': 0xF8FB,
  'bracketrightex': 0xF8FA,
  'bracketrightmonospace': 0xFF3D,
  'bracketrighttp': 0xF8F9,
  'breve': 0x02D8,
  'brevebelowcmb': 0x032E,
  'brevecmb': 0x0306,
  'breveinvertedbelowcmb': 0x032F,
  'breveinvertedcmb': 0x0311,
  'breveinverteddoublecmb': 0x0361,
  'bridgebelowcmb': 0x032A,
  'bridgeinvertedbelowcmb': 0x033A,
  'brokenbar': 0x00A6,
  'bstroke': 0x0180,
  'bsuperior': 0xF6EA,
  'btopbar': 0x0183,
  'buhiragana': 0x3076,
  'bukatakana': 0x30D6,
  'bullet': 0x2022,
  'bulletinverse': 0x25D8,
  'bulletoperator': 0x2219,
  'bullseye': 0x25CE,
  'c': 0x0063,
  'caarmenian': 0x056E,
  'cabengali': 0x099A,
  'cacute': 0x0107,
  'cadeva': 0x091A,
  'cagujarati': 0x0A9A,
  'cagurmukhi': 0x0A1A,
  'calsquare': 0x3388,
  'candrabindubengali': 0x0981,
  'candrabinducmb': 0x0310,
  'candrabindudeva': 0x0901,
  'candrabindugujarati': 0x0A81,
  'capslock': 0x21EA,
  'careof': 0x2105,
  'caron': 0x02C7,
  'caronbelowcmb': 0x032C,
  'caroncmb': 0x030C,
  'carriagereturn': 0x21B5,
  'cbopomofo': 0x3118,
  'ccaron': 0x010D,
  'ccedilla': 0x00E7,
  'ccedillaacute': 0x1E09,
  'ccircle': 0x24D2,
  'ccircumflex': 0x0109,
  'ccurl': 0x0255,
  'cdot': 0x010B,
  'cdotaccent': 0x010B,
  'cdsquare': 0x33C5,
  'cedilla': 0x00B8,
  'cedillacmb': 0x0327,
  'cent': 0x00A2,
  'centigrade': 0x2103,
  'centinferior': 0xF6DF,
  'centmonospace': 0xFFE0,
  'centoldstyle': 0xF7A2,
  'centsuperior': 0xF6E0,
  'chaarmenian': 0x0579,
  'chabengali': 0x099B,
  'chadeva': 0x091B,
  'chagujarati': 0x0A9B,
  'chagurmukhi': 0x0A1B,
  'chbopomofo': 0x3114,
  'cheabkhasiancyrillic': 0x04BD,
  'checkmark': 0x2713,
  'checyrillic': 0x0447,
  'chedescenderabkhasiancyrillic': 0x04BF,
  'chedescendercyrillic': 0x04B7,
  'chedieresiscyrillic': 0x04F5,
  'cheharmenian': 0x0573,
  'chekhakassiancyrillic': 0x04CC,
  'cheverticalstrokecyrillic': 0x04B9,
  'chi': 0x03C7,
  'chieuchacirclekorean': 0x3277,
  'chieuchaparenkorean': 0x3217,
  'chieuchcirclekorean': 0x3269,
  'chieuchkorean': 0x314A,
  'chieuchparenkorean': 0x3209,
  'chochangthai': 0x0E0A,
  'chochanthai': 0x0E08,
  'chochingthai': 0x0E09,
  'chochoethai': 0x0E0C,
  'chook': 0x0188,
  'cieucacirclekorean': 0x3276,
  'cieucaparenkorean': 0x3216,
  'cieuccirclekorean': 0x3268,
  'cieuckorean': 0x3148,
  'cieucparenkorean': 0x3208,
  'cieucuparenkorean': 0x321C,
  'circle': 0x25CB,
  'circlecopyrt': 0x00A9,
  'circlemultiply': 0x2297,
  'circleot': 0x2299,
  'circleplus': 0x2295,
  'circlepostalmark': 0x3036,
  'circlewithlefthalfblack': 0x25D0,
  'circlewithrighthalfblack': 0x25D1,
  'circumflex': 0x02C6,
  'circumflexbelowcmb': 0x032D,
  'circumflexcmb': 0x0302,
  'clear': 0x2327,
  'clickalveolar': 0x01C2,
  'clickdental': 0x01C0,
  'clicklateral': 0x01C1,
  'clickretroflex': 0x01C3,
  'club': 0x2663,
  'clubsuitblack': 0x2663,
  'clubsuitwhite': 0x2667,
  'cmcubedsquare': 0x33A4,
  'cmonospace': 0xFF43,
  'cmsquaredsquare': 0x33A0,
  'coarmenian': 0x0581,
  'colon': 0x003A,
  'colonmonetary': 0x20A1,
  'colonmonospace': 0xFF1A,
  'colonsign': 0x20A1,
  'colonsmall': 0xFE55,
  'colontriangularhalfmod': 0x02D1,
  'colontriangularmod': 0x02D0,
  'comma': 0x002C,
  'commaabovecmb': 0x0313,
  'commaaboverightcmb': 0x0315,
  'commaaccent': 0xF6C3,
  'commaarabic': 0x060C,
  'commaarmenian': 0x055D,
  'commainferior': 0xF6E1,
  'commamonospace': 0xFF0C,
  'commareversedabovecmb': 0x0314,
  'commareversedmod': 0x02BD,
  'commasmall': 0xFE50,
  'commasuperior': 0xF6E2,
  'commaturnedabovecmb': 0x0312,
  'commaturnedmod': 0x02BB,
  'compass': 0x263C,
  'congruent': 0x2245,
  'contourintegral': 0x222E,
  'control': 0x2303,
  'controlACK': 0x0006,
  'controlBEL': 0x0007,
  'controlBS': 0x0008,
  'controlCAN': 0x0018,
  'controlCR': 0x000D,
  'controlDC1': 0x0011,
  'controlDC2': 0x0012,
  'controlDC3': 0x0013,
  'controlDC4': 0x0014,
  'controlDEL': 0x007F,
  'controlDLE': 0x0010,
  'controlEM': 0x0019,
  'controlENQ': 0x0005,
  'controlEOT': 0x0004,
  'controlESC': 0x001B,
  'controlETB': 0x0017,
  'controlETX': 0x0003,
  'controlFF': 0x000C,
  'controlFS': 0x001C,
  'controlGS': 0x001D,
  'controlHT': 0x0009,
  'controlLF': 0x000A,
  'controlNAK': 0x0015,
  'controlRS': 0x001E,
  'controlSI': 0x000F,
  'controlSO': 0x000E,
  'controlSOT': 0x0002,
  'controlSTX': 0x0001,
  'controlSUB': 0x001A,
  'controlSYN': 0x0016,
  'controlUS': 0x001F,
  'controlVT': 0x000B,
  'copyright': 0x00A9,
  'copyrightsans': 0xF8E9,
  'copyrightserif': 0xF6D9,
  'cornerbracketleft': 0x300C,
  'cornerbracketlefthalfwidth': 0xFF62,
  'cornerbracketleftvertical': 0xFE41,
  'cornerbracketright': 0x300D,
  'cornerbracketrighthalfwidth': 0xFF63,
  'cornerbracketrightvertical': 0xFE42,
  'corporationsquare': 0x337F,
  'cosquare': 0x33C7,
  'coverkgsquare': 0x33C6,
  'cparen': 0x249E,
  'cruzeiro': 0x20A2,
  'cstretched': 0x0297,
  'curlyand': 0x22CF,
  'curlyor': 0x22CE,
  'currency': 0x00A4,
  'cyrBreve': 0xF6D1,
  'cyrFlex': 0xF6D2,
  'cyrbreve': 0xF6D4,
  'cyrflex': 0xF6D5,
  'd': 0x0064,
  'daarmenian': 0x0564,
  'dabengali': 0x09A6,
  'dadarabic': 0x0636,
  'dadeva': 0x0926,
  'dadfinalarabic': 0xFEBE,
  'dadinitialarabic': 0xFEBF,
  'dadmedialarabic': 0xFEC0,
  'dagesh': 0x05BC,
  'dageshhebrew': 0x05BC,
  'dagger': 0x2020,
  'daggerdbl': 0x2021,
  'dagujarati': 0x0AA6,
  'dagurmukhi': 0x0A26,
  'dahiragana': 0x3060,
  'dakatakana': 0x30C0,
  'dalarabic': 0x062F,
  'dalet': 0x05D3,
  'daletdagesh': 0xFB33,
  'daletdageshhebrew': 0xFB33,
  'dalethebrew': 0x05D3,
  'dalfinalarabic': 0xFEAA,
  'dammaarabic': 0x064F,
  'dammalowarabic': 0x064F,
  'dammatanaltonearabic': 0x064C,
  'dammatanarabic': 0x064C,
  'danda': 0x0964,
  'dargahebrew': 0x05A7,
  'dargalefthebrew': 0x05A7,
  'dasiapneumatacyrilliccmb': 0x0485,
  'dblGrave': 0xF6D3,
  'dblanglebracketleft': 0x300A,
  'dblanglebracketleftvertical': 0xFE3D,
  'dblanglebracketright': 0x300B,
  'dblanglebracketrightvertical': 0xFE3E,
  'dblarchinvertedbelowcmb': 0x032B,
  'dblarrowleft': 0x21D4,
  'dblarrowright': 0x21D2,
  'dbldanda': 0x0965,
  'dblgrave': 0xF6D6,
  'dblgravecmb': 0x030F,
  'dblintegral': 0x222C,
  'dbllowline': 0x2017,
  'dbllowlinecmb': 0x0333,
  'dbloverlinecmb': 0x033F,
  'dblprimemod': 0x02BA,
  'dblverticalbar': 0x2016,
  'dblverticallineabovecmb': 0x030E,
  'dbopomofo': 0x3109,
  'dbsquare': 0x33C8,
  'dcaron': 0x010F,
  'dcedilla': 0x1E11,
  'dcircle': 0x24D3,
  'dcircumflexbelow': 0x1E13,
  'dcroat': 0x0111,
  'ddabengali': 0x09A1,
  'ddadeva': 0x0921,
  'ddagujarati': 0x0AA1,
  'ddagurmukhi': 0x0A21,
  'ddalarabic': 0x0688,
  'ddalfinalarabic': 0xFB89,
  'dddhadeva': 0x095C,
  'ddhabengali': 0x09A2,
  'ddhadeva': 0x0922,
  'ddhagujarati': 0x0AA2,
  'ddhagurmukhi': 0x0A22,
  'ddotaccent': 0x1E0B,
  'ddotbelow': 0x1E0D,
  'decimalseparatorarabic': 0x066B,
  'decimalseparatorpersian': 0x066B,
  'decyrillic': 0x0434,
  'degree': 0x00B0,
  'dehihebrew': 0x05AD,
  'dehiragana': 0x3067,
  'deicoptic': 0x03EF,
  'dekatakana': 0x30C7,
  'deleteleft': 0x232B,
  'deleteright': 0x2326,
  'delta': 0x03B4,
  'deltaturned': 0x018D,
  'denominatorminusonenumeratorbengali': 0x09F8,
  'dezh': 0x02A4,
  'dhabengali': 0x09A7,
  'dhadeva': 0x0927,
  'dhagujarati': 0x0AA7,
  'dhagurmukhi': 0x0A27,
  'dhook': 0x0257,
  'dialytikatonos': 0x0385,
  'dialytikatonoscmb': 0x0344,
  'diamond': 0x2666,
  'diamondsuitwhite': 0x2662,
  'dieresis': 0x00A8,
  'dieresisacute': 0xF6D7,
  'dieresisbelowcmb': 0x0324,
  'dieresiscmb': 0x0308,
  'dieresisgrave': 0xF6D8,
  'dieresistonos': 0x0385,
  'dihiragana': 0x3062,
  'dikatakana': 0x30C2,
  'dittomark': 0x3003,
  'divide': 0x00F7,
  'divides': 0x2223,
  'divisionslash': 0x2215,
  'djecyrillic': 0x0452,
  'dkshade': 0x2593,
  'dlinebelow': 0x1E0F,
  'dlsquare': 0x3397,
  'dmacron': 0x0111,
  'dmonospace': 0xFF44,
  'dnblock': 0x2584,
  'dochadathai': 0x0E0E,
  'dodekthai': 0x0E14,
  'dohiragana': 0x3069,
  'dokatakana': 0x30C9,
  'dollar': 0x0024,
  'dollarinferior': 0xF6E3,
  'dollarmonospace': 0xFF04,
  'dollaroldstyle': 0xF724,
  'dollarsmall': 0xFE69,
  'dollarsuperior': 0xF6E4,
  'dong': 0x20AB,
  'dorusquare': 0x3326,
  'dotaccent': 0x02D9,
  'dotaccentcmb': 0x0307,
  'dotbelowcmb': 0x0323,
  'dotbelowcomb': 0x0323,
  'dotkatakana': 0x30FB,
  'dotlessi': 0x0131,
  'dotlessj': 0xF6BE,
  'dotlessjstrokehook': 0x0284,
  'dotmath': 0x22C5,
  'dottedcircle': 0x25CC,
  'doubleyodpatah': 0xFB1F,
  'doubleyodpatahhebrew': 0xFB1F,
  'downtackbelowcmb': 0x031E,
  'downtackmod': 0x02D5,
  'dparen': 0x249F,
  'dsuperior': 0xF6EB,
  'dtail': 0x0256,
  'dtopbar': 0x018C,
  'duhiragana': 0x3065,
  'dukatakana': 0x30C5,
  'dz': 0x01F3,
  'dzaltone': 0x02A3,
  'dzcaron': 0x01C6,
  'dzcurl': 0x02A5,
  'dzeabkhasiancyrillic': 0x04E1,
  'dzecyrillic': 0x0455,
  'dzhecyrillic': 0x045F,
  'e': 0x0065,
  'eacute': 0x00E9,
  'earth': 0x2641,
  'ebengali': 0x098F,
  'ebopomofo': 0x311C,
  'ebreve': 0x0115,
  'ecandradeva': 0x090D,
  'ecandragujarati': 0x0A8D,
  'ecandravowelsigndeva': 0x0945,
  'ecandravowelsigngujarati': 0x0AC5,
  'ecaron': 0x011B,
  'ecedillabreve': 0x1E1D,
  'echarmenian': 0x0565,
  'echyiwnarmenian': 0x0587,
  'ecircle': 0x24D4,
  'ecircumflex': 0x00EA,
  'ecircumflexacute': 0x1EBF,
  'ecircumflexbelow': 0x1E19,
  'ecircumflexdotbelow': 0x1EC7,
  'ecircumflexgrave': 0x1EC1,
  'ecircumflexhookabove': 0x1EC3,
  'ecircumflextilde': 0x1EC5,
  'ecyrillic': 0x0454,
  'edblgrave': 0x0205,
  'edeva': 0x090F,
  'edieresis': 0x00EB,
  'edot': 0x0117,
  'edotaccent': 0x0117,
  'edotbelow': 0x1EB9,
  'eegurmukhi': 0x0A0F,
  'eematragurmukhi': 0x0A47,
  'efcyrillic': 0x0444,
  'egrave': 0x00E8,
  'egujarati': 0x0A8F,
  'eharmenian': 0x0567,
  'ehbopomofo': 0x311D,
  'ehiragana': 0x3048,
  'ehookabove': 0x1EBB,
  'eibopomofo': 0x311F,
  'eight': 0x0038,
  'eightarabic': 0x0668,
  'eightbengali': 0x09EE,
  'eightcircle': 0x2467,
  'eightcircleinversesansserif': 0x2791,
  'eightdeva': 0x096E,
  'eighteencircle': 0x2471,
  'eighteenparen': 0x2485,
  'eighteenperiod': 0x2499,
  'eightgujarati': 0x0AEE,
  'eightgurmukhi': 0x0A6E,
  'eighthackarabic': 0x0668,
  'eighthangzhou': 0x3028,
  'eighthnotebeamed': 0x266B,
  'eightideographicparen': 0x3227,
  'eightinferior': 0x2088,
  'eightmonospace': 0xFF18,
  'eightoldstyle': 0xF738,
  'eightparen': 0x247B,
  'eightperiod': 0x248F,
  'eightpersian': 0x06F8,
  'eightroman': 0x2177,
  'eightsuperior': 0x2078,
  'eightthai': 0x0E58,
  'einvertedbreve': 0x0207,
  'eiotifiedcyrillic': 0x0465,
  'ekatakana': 0x30A8,
  'ekatakanahalfwidth': 0xFF74,
  'ekonkargurmukhi': 0x0A74,
  'ekorean': 0x3154,
  'elcyrillic': 0x043B,
  'element': 0x2208,
  'elevencircle': 0x246A,
  'elevenparen': 0x247E,
  'elevenperiod': 0x2492,
  'elevenroman': 0x217A,
  'ellipsis': 0x2026,
  'ellipsisvertical': 0x22EE,
  'emacron': 0x0113,
  'emacronacute': 0x1E17,
  'emacrongrave': 0x1E15,
  'emcyrillic': 0x043C,
  'emdash': 0x2014,
  'emdashvertical': 0xFE31,
  'emonospace': 0xFF45,
  'emphasismarkarmenian': 0x055B,
  'emptyset': 0x2205,
  'enbopomofo': 0x3123,
  'encyrillic': 0x043D,
  'endash': 0x2013,
  'endashvertical': 0xFE32,
  'endescendercyrillic': 0x04A3,
  'eng': 0x014B,
  'engbopomofo': 0x3125,
  'enghecyrillic': 0x04A5,
  'enhookcyrillic': 0x04C8,
  'enspace': 0x2002,
  'eogonek': 0x0119,
  'eokorean': 0x3153,
  'eopen': 0x025B,
  'eopenclosed': 0x029A,
  'eopenreversed': 0x025C,
  'eopenreversedclosed': 0x025E,
  'eopenreversedhook': 0x025D,
  'eparen': 0x24A0,
  'epsilon': 0x03B5,
  'epsilontonos': 0x03AD,
  'equal': 0x003D,
  'equalmonospace': 0xFF1D,
  'equalsmall': 0xFE66,
  'equalsuperior': 0x207C,
  'equivalence': 0x2261,
  'erbopomofo': 0x3126,
  'ercyrillic': 0x0440,
  'ereversed': 0x0258,
  'ereversedcyrillic': 0x044D,
  'escyrillic': 0x0441,
  'esdescendercyrillic': 0x04AB,
  'esh': 0x0283,
  'eshcurl': 0x0286,
  'eshortdeva': 0x090E,
  'eshortvowelsigndeva': 0x0946,
  'eshreversedloop': 0x01AA,
  'eshsquatreversed': 0x0285,
  'esmallhiragana': 0x3047,
  'esmallkatakana': 0x30A7,
  'esmallkatakanahalfwidth': 0xFF6A,
  'estimated': 0x212E,
  'esuperior': 0xF6EC,
  'eta': 0x03B7,
  'etarmenian': 0x0568,
  'etatonos': 0x03AE,
  'eth': 0x00F0,
  'etilde': 0x1EBD,
  'etildebelow': 0x1E1B,
  'etnahtafoukhhebrew': 0x0591,
  'etnahtafoukhlefthebrew': 0x0591,
  'etnahtahebrew': 0x0591,
  'etnahtalefthebrew': 0x0591,
  'eturned': 0x01DD,
  'eukorean': 0x3161,
  'euro': 0x20AC,
  'evowelsignbengali': 0x09C7,
  'evowelsigndeva': 0x0947,
  'evowelsigngujarati': 0x0AC7,
  'exclam': 0x0021,
  'exclamarmenian': 0x055C,
  'exclamdbl': 0x203C,
  'exclamdown': 0x00A1,
  'exclamdownsmall': 0xF7A1,
  'exclammonospace': 0xFF01,
  'exclamsmall': 0xF721,
  'existential': 0x2203,
  'ezh': 0x0292,
  'ezhcaron': 0x01EF,
  'ezhcurl': 0x0293,
  'ezhreversed': 0x01B9,
  'ezhtail': 0x01BA,
  'f': 0x0066,
  'fadeva': 0x095E,
  'fagurmukhi': 0x0A5E,
  'fahrenheit': 0x2109,
  'fathaarabic': 0x064E,
  'fathalowarabic': 0x064E,
  'fathatanarabic': 0x064B,
  'fbopomofo': 0x3108,
  'fcircle': 0x24D5,
  'fdotaccent': 0x1E1F,
  'feharabic': 0x0641,
  'feharmenian': 0x0586,
  'fehfinalarabic': 0xFED2,
  'fehinitialarabic': 0xFED3,
  'fehmedialarabic': 0xFED4,
  'feicoptic': 0x03E5,
  'female': 0x2640,
  'ff': 0xFB00,
  'ffi': 0xFB03,
  'ffl': 0xFB04,
  'fi': 0xFB01,
  'fifteencircle': 0x246E,
  'fifteenparen': 0x2482,
  'fifteenperiod': 0x2496,
  'figuredash': 0x2012,
  'filledbox': 0x25A0,
  'filledrect': 0x25AC,
  'finalkaf': 0x05DA,
  'finalkafdagesh': 0xFB3A,
  'finalkafdageshhebrew': 0xFB3A,
  'finalkafhebrew': 0x05DA,
  'finalmem': 0x05DD,
  'finalmemhebrew': 0x05DD,
  'finalnun': 0x05DF,
  'finalnunhebrew': 0x05DF,
  'finalpe': 0x05E3,
  'finalpehebrew': 0x05E3,
  'finaltsadi': 0x05E5,
  'finaltsadihebrew': 0x05E5,
  'firsttonechinese': 0x02C9,
  'fisheye': 0x25C9,
  'fitacyrillic': 0x0473,
  'five': 0x0035,
  'fivearabic': 0x0665,
  'fivebengali': 0x09EB,
  'fivecircle': 0x2464,
  'fivecircleinversesansserif': 0x278E,
  'fivedeva': 0x096B,
  'fiveeighths': 0x215D,
  'fivegujarati': 0x0AEB,
  'fivegurmukhi': 0x0A6B,
  'fivehackarabic': 0x0665,
  'fivehangzhou': 0x3025,
  'fiveideographicparen': 0x3224,
  'fiveinferior': 0x2085,
  'fivemonospace': 0xFF15,
  'fiveoldstyle': 0xF735,
  'fiveparen': 0x2478,
  'fiveperiod': 0x248C,
  'fivepersian': 0x06F5,
  'fiveroman': 0x2174,
  'fivesuperior': 0x2075,
  'fivethai': 0x0E55,
  'fl': 0xFB02,
  'florin': 0x0192,
  'fmonospace': 0xFF46,
  'fmsquare': 0x3399,
  'fofanthai': 0x0E1F,
  'fofathai': 0x0E1D,
  'fongmanthai': 0x0E4F,
  'forall': 0x2200,
  'four': 0x0034,
  'fourarabic': 0x0664,
  'fourbengali': 0x09EA,
  'fourcircle': 0x2463,
  'fourcircleinversesansserif': 0x278D,
  'fourdeva': 0x096A,
  'fourgujarati': 0x0AEA,
  'fourgurmukhi': 0x0A6A,
  'fourhackarabic': 0x0664,
  'fourhangzhou': 0x3024,
  'fourideographicparen': 0x3223,
  'fourinferior': 0x2084,
  'fourmonospace': 0xFF14,
  'fournumeratorbengali': 0x09F7,
  'fouroldstyle': 0xF734,
  'fourparen': 0x2477,
  'fourperiod': 0x248B,
  'fourpersian': 0x06F4,
  'fourroman': 0x2173,
  'foursuperior': 0x2074,
  'fourteencircle': 0x246D,
  'fourteenparen': 0x2481,
  'fourteenperiod': 0x2495,
  'fourthai': 0x0E54,
  'fourthtonechinese': 0x02CB,
  'fparen': 0x24A1,
  'fraction': 0x2044,
  'franc': 0x20A3,
  'g': 0x0067,
  'gabengali': 0x0997,
  'gacute': 0x01F5,
  'gadeva': 0x0917,
  'gafarabic': 0x06AF,
  'gaffinalarabic': 0xFB93,
  'gafinitialarabic': 0xFB94,
  'gafmedialarabic': 0xFB95,
  'gagujarati': 0x0A97,
  'gagurmukhi': 0x0A17,
  'gahiragana': 0x304C,
  'gakatakana': 0x30AC,
  'gamma': 0x03B3,
  'gammalatinsmall': 0x0263,
  'gammasuperior': 0x02E0,
  'gangiacoptic': 0x03EB,
  'gbopomofo': 0x310D,
  'gbreve': 0x011F,
  'gcaron': 0x01E7,
  'gcedilla': 0x0123,
  'gcircle': 0x24D6,
  'gcircumflex': 0x011D,
  'gcommaaccent': 0x0123,
  'gdot': 0x0121,
  'gdotaccent': 0x0121,
  'gecyrillic': 0x0433,
  'gehiragana': 0x3052,
  'gekatakana': 0x30B2,
  'geometricallyequal': 0x2251,
  'gereshaccenthebrew': 0x059C,
  'gereshhebrew': 0x05F3,
  'gereshmuqdamhebrew': 0x059D,
  'germandbls': 0x00DF,
  'gershayimaccenthebrew': 0x059E,
  'gershayimhebrew': 0x05F4,
  'getamark': 0x3013,
  'ghabengali': 0x0998,
  'ghadarmenian': 0x0572,
  'ghadeva': 0x0918,
  'ghagujarati': 0x0A98,
  'ghagurmukhi': 0x0A18,
  'ghainarabic': 0x063A,
  'ghainfinalarabic': 0xFECE,
  'ghaininitialarabic': 0xFECF,
  'ghainmedialarabic': 0xFED0,
  'ghemiddlehookcyrillic': 0x0495,
  'ghestrokecyrillic': 0x0493,
  'gheupturncyrillic': 0x0491,
  'ghhadeva': 0x095A,
  'ghhagurmukhi': 0x0A5A,
  'ghook': 0x0260,
  'ghzsquare': 0x3393,
  'gihiragana': 0x304E,
  'gikatakana': 0x30AE,
  'gimarmenian': 0x0563,
  'gimel': 0x05D2,
  'gimeldagesh': 0xFB32,
  'gimeldageshhebrew': 0xFB32,
  'gimelhebrew': 0x05D2,
  'gjecyrillic': 0x0453,
  'glottalinvertedstroke': 0x01BE,
  'glottalstop': 0x0294,
  'glottalstopinverted': 0x0296,
  'glottalstopmod': 0x02C0,
  'glottalstopreversed': 0x0295,
  'glottalstopreversedmod': 0x02C1,
  'glottalstopreversedsuperior': 0x02E4,
  'glottalstopstroke': 0x02A1,
  'glottalstopstrokereversed': 0x02A2,
  'gmacron': 0x1E21,
  'gmonospace': 0xFF47,
  'gohiragana': 0x3054,
  'gokatakana': 0x30B4,
  'gparen': 0x24A2,
  'gpasquare': 0x33AC,
  'gradient': 0x2207,
  'grave': 0x0060,
  'gravebelowcmb': 0x0316,
  'gravecmb': 0x0300,
  'gravecomb': 0x0300,
  'gravedeva': 0x0953,
  'gravelowmod': 0x02CE,
  'gravemonospace': 0xFF40,
  'gravetonecmb': 0x0340,
  'greater': 0x003E,
  'greaterequal': 0x2265,
  'greaterequalorless': 0x22DB,
  'greatermonospace': 0xFF1E,
  'greaterorequivalent': 0x2273,
  'greaterorless': 0x2277,
  'greateroverequal': 0x2267,
  'greatersmall': 0xFE65,
  'gscript': 0x0261,
  'gstroke': 0x01E5,
  'guhiragana': 0x3050,
  'guillemotleft': 0x00AB,
  'guillemotright': 0x00BB,
  'guilsinglleft': 0x2039,
  'guilsinglright': 0x203A,
  'gukatakana': 0x30B0,
  'guramusquare': 0x3318,
  'gysquare': 0x33C9,
  'h': 0x0068,
  'haabkhasiancyrillic': 0x04A9,
  'haaltonearabic': 0x06C1,
  'habengali': 0x09B9,
  'hadescendercyrillic': 0x04B3,
  'hadeva': 0x0939,
  'hagujarati': 0x0AB9,
  'hagurmukhi': 0x0A39,
  'haharabic': 0x062D,
  'hahfinalarabic': 0xFEA2,
  'hahinitialarabic': 0xFEA3,
  'hahiragana': 0x306F,
  'hahmedialarabic': 0xFEA4,
  'haitusquare': 0x332A,
  'hakatakana': 0x30CF,
  'hakatakanahalfwidth': 0xFF8A,
  'halantgurmukhi': 0x0A4D,
  'hamzaarabic': 0x0621,
  'hamzalowarabic': 0x0621,
  'hangulfiller': 0x3164,
  'hardsigncyrillic': 0x044A,
  'harpoonleftbarbup': 0x21BC,
  'harpoonrightbarbup': 0x21C0,
  'hasquare': 0x33CA,
  'hatafpatah': 0x05B2,
  'hatafpatah16': 0x05B2,
  'hatafpatah23': 0x05B2,
  'hatafpatah2f': 0x05B2,
  'hatafpatahhebrew': 0x05B2,
  'hatafpatahnarrowhebrew': 0x05B2,
  'hatafpatahquarterhebrew': 0x05B2,
  'hatafpatahwidehebrew': 0x05B2,
  'hatafqamats': 0x05B3,
  'hatafqamats1b': 0x05B3,
  'hatafqamats28': 0x05B3,
  'hatafqamats34': 0x05B3,
  'hatafqamatshebrew': 0x05B3,
  'hatafqamatsnarrowhebrew': 0x05B3,
  'hatafqamatsquarterhebrew': 0x05B3,
  'hatafqamatswidehebrew': 0x05B3,
  'hatafsegol': 0x05B1,
  'hatafsegol17': 0x05B1,
  'hatafsegol24': 0x05B1,
  'hatafsegol30': 0x05B1,
  'hatafsegolhebrew': 0x05B1,
  'hatafsegolnarrowhebrew': 0x05B1,
  'hatafsegolquarterhebrew': 0x05B1,
  'hatafsegolwidehebrew': 0x05B1,
  'hbar': 0x0127,
  'hbopomofo': 0x310F,
  'hbrevebelow': 0x1E2B,
  'hcedilla': 0x1E29,
  'hcircle': 0x24D7,
  'hcircumflex': 0x0125,
  'hdieresis': 0x1E27,
  'hdotaccent': 0x1E23,
  'hdotbelow': 0x1E25,
  'he': 0x05D4,
  'heart': 0x2665,
  'heartsuitblack': 0x2665,
  'heartsuitwhite': 0x2661,
  'hedagesh': 0xFB34,
  'hedageshhebrew': 0xFB34,
  'hehaltonearabic': 0x06C1,
  'heharabic': 0x0647,
  'hehebrew': 0x05D4,
  'hehfinalaltonearabic': 0xFBA7,
  'hehfinalalttwoarabic': 0xFEEA,
  'hehfinalarabic': 0xFEEA,
  'hehhamzaabovefinalarabic': 0xFBA5,
  'hehhamzaaboveisolatedarabic': 0xFBA4,
  'hehinitialaltonearabic': 0xFBA8,
  'hehinitialarabic': 0xFEEB,
  'hehiragana': 0x3078,
  'hehmedialaltonearabic': 0xFBA9,
  'hehmedialarabic': 0xFEEC,
  'heiseierasquare': 0x337B,
  'hekatakana': 0x30D8,
  'hekatakanahalfwidth': 0xFF8D,
  'hekutaarusquare': 0x3336,
  'henghook': 0x0267,
  'herutusquare': 0x3339,
  'het': 0x05D7,
  'hethebrew': 0x05D7,
  'hhook': 0x0266,
  'hhooksuperior': 0x02B1,
  'hieuhacirclekorean': 0x327B,
  'hieuhaparenkorean': 0x321B,
  'hieuhcirclekorean': 0x326D,
  'hieuhkorean': 0x314E,
  'hieuhparenkorean': 0x320D,
  'hihiragana': 0x3072,
  'hikatakana': 0x30D2,
  'hikatakanahalfwidth': 0xFF8B,
  'hiriq': 0x05B4,
  'hiriq14': 0x05B4,
  'hiriq21': 0x05B4,
  'hiriq2d': 0x05B4,
  'hiriqhebrew': 0x05B4,
  'hiriqnarrowhebrew': 0x05B4,
  'hiriqquarterhebrew': 0x05B4,
  'hiriqwidehebrew': 0x05B4,
  'hlinebelow': 0x1E96,
  'hmonospace': 0xFF48,
  'hoarmenian': 0x0570,
  'hohipthai': 0x0E2B,
  'hohiragana': 0x307B,
  'hokatakana': 0x30DB,
  'hokatakanahalfwidth': 0xFF8E,
  'holam': 0x05B9,
  'holam19': 0x05B9,
  'holam26': 0x05B9,
  'holam32': 0x05B9,
  'holamhebrew': 0x05B9,
  'holamnarrowhebrew': 0x05B9,
  'holamquarterhebrew': 0x05B9,
  'holamwidehebrew': 0x05B9,
  'honokhukthai': 0x0E2E,
  'hookabovecomb': 0x0309,
  'hookcmb': 0x0309,
  'hookpalatalizedbelowcmb': 0x0321,
  'hookretroflexbelowcmb': 0x0322,
  'hoonsquare': 0x3342,
  'horicoptic': 0x03E9,
  'horizontalbar': 0x2015,
  'horncmb': 0x031B,
  'hotsprings': 0x2668,
  'house': 0x2302,
  'hparen': 0x24A3,
  'hsuperior': 0x02B0,
  'hturned': 0x0265,
  'huhiragana': 0x3075,
  'huiitosquare': 0x3333,
  'hukatakana': 0x30D5,
  'hukatakanahalfwidth': 0xFF8C,
  'hungarumlaut': 0x02DD,
  'hungarumlautcmb': 0x030B,
  'hv': 0x0195,
  'hyphen': 0x002D,
  'hypheninferior': 0xF6E5,
  'hyphenmonospace': 0xFF0D,
  'hyphensmall': 0xFE63,
  'hyphensuperior': 0xF6E6,
  'hyphentwo': 0x2010,
  'i': 0x0069,
  'iacute': 0x00ED,
  'iacyrillic': 0x044F,
  'ibengali': 0x0987,
  'ibopomofo': 0x3127,
  'ibreve': 0x012D,
  'icaron': 0x01D0,
  'icircle': 0x24D8,
  'icircumflex': 0x00EE,
  'icyrillic': 0x0456,
  'idblgrave': 0x0209,
  'ideographearthcircle': 0x328F,
  'ideographfirecircle': 0x328B,
  'ideographicallianceparen': 0x323F,
  'ideographiccallparen': 0x323A,
  'ideographiccentrecircle': 0x32A5,
  'ideographicclose': 0x3006,
  'ideographiccomma': 0x3001,
  'ideographiccommaleft': 0xFF64,
  'ideographiccongratulationparen': 0x3237,
  'ideographiccorrectcircle': 0x32A3,
  'ideographicearthparen': 0x322F,
  'ideographicenterpriseparen': 0x323D,
  'ideographicexcellentcircle': 0x329D,
  'ideographicfestivalparen': 0x3240,
  'ideographicfinancialcircle': 0x3296,
  'ideographicfinancialparen': 0x3236,
  'ideographicfireparen': 0x322B,
  'ideographichaveparen': 0x3232,
  'ideographichighcircle': 0x32A4,
  'ideographiciterationmark': 0x3005,
  'ideographiclaborcircle': 0x3298,
  'ideographiclaborparen': 0x3238,
  'ideographicleftcircle': 0x32A7,
  'ideographiclowcircle': 0x32A6,
  'ideographicmedicinecircle': 0x32A9,
  'ideographicmetalparen': 0x322E,
  'ideographicmoonparen': 0x322A,
  'ideographicnameparen': 0x3234,
  'ideographicperiod': 0x3002,
  'ideographicprintcircle': 0x329E,
  'ideographicreachparen': 0x3243,
  'ideographicrepresentparen': 0x3239,
  'ideographicresourceparen': 0x323E,
  'ideographicrightcircle': 0x32A8,
  'ideographicsecretcircle': 0x3299,
  'ideographicselfparen': 0x3242,
  'ideographicsocietyparen': 0x3233,
  'ideographicspace': 0x3000,
  'ideographicspecialparen': 0x3235,
  'ideographicstockparen': 0x3231,
  'ideographicstudyparen': 0x323B,
  'ideographicsunparen': 0x3230,
  'ideographicsuperviseparen': 0x323C,
  'ideographicwaterparen': 0x322C,
  'ideographicwoodparen': 0x322D,
  'ideographiczero': 0x3007,
  'ideographmetalcircle': 0x328E,
  'ideographmooncircle': 0x328A,
  'ideographnamecircle': 0x3294,
  'ideographsuncircle': 0x3290,
  'ideographwatercircle': 0x328C,
  'ideographwoodcircle': 0x328D,
  'ideva': 0x0907,
  'idieresis': 0x00EF,
  'idieresisacute': 0x1E2F,
  'idieresiscyrillic': 0x04E5,
  'idotbelow': 0x1ECB,
  'iebrevecyrillic': 0x04D7,
  'iecyrillic': 0x0435,
  'ieungacirclekorean': 0x3275,
  'ieungaparenkorean': 0x3215,
  'ieungcirclekorean': 0x3267,
  'ieungkorean': 0x3147,
  'ieungparenkorean': 0x3207,
  'igrave': 0x00EC,
  'igujarati': 0x0A87,
  'igurmukhi': 0x0A07,
  'ihiragana': 0x3044,
  'ihookabove': 0x1EC9,
  'iibengali': 0x0988,
  'iicyrillic': 0x0438,
  'iideva': 0x0908,
  'iigujarati': 0x0A88,
  'iigurmukhi': 0x0A08,
  'iimatragurmukhi': 0x0A40,
  'iinvertedbreve': 0x020B,
  'iishortcyrillic': 0x0439,
  'iivowelsignbengali': 0x09C0,
  'iivowelsigndeva': 0x0940,
  'iivowelsigngujarati': 0x0AC0,
  'ij': 0x0133,
  'ikatakana': 0x30A4,
  'ikatakanahalfwidth': 0xFF72,
  'ikorean': 0x3163,
  'ilde': 0x02DC,
  'iluyhebrew': 0x05AC,
  'imacron': 0x012B,
  'imacroncyrillic': 0x04E3,
  'imageorapproximatelyequal': 0x2253,
  'imatragurmukhi': 0x0A3F,
  'imonospace': 0xFF49,
  'increment': 0x2206,
  'infinity': 0x221E,
  'iniarmenian': 0x056B,
  'integral': 0x222B,
  'integralbottom': 0x2321,
  'integralbt': 0x2321,
  'integralex': 0xF8F5,
  'integraltop': 0x2320,
  'integraltp': 0x2320,
  'intersection': 0x2229,
  'intisquare': 0x3305,
  'invbullet': 0x25D8,
  'invcircle': 0x25D9,
  'invsmileface': 0x263B,
  'iocyrillic': 0x0451,
  'iogonek': 0x012F,
  'iota': 0x03B9,
  'iotadieresis': 0x03CA,
  'iotadieresistonos': 0x0390,
  'iotalatin': 0x0269,
  'iotatonos': 0x03AF,
  'iparen': 0x24A4,
  'irigurmukhi': 0x0A72,
  'ismallhiragana': 0x3043,
  'ismallkatakana': 0x30A3,
  'ismallkatakanahalfwidth': 0xFF68,
  'issharbengali': 0x09FA,
  'istroke': 0x0268,
  'isuperior': 0xF6ED,
  'iterationhiragana': 0x309D,
  'iterationkatakana': 0x30FD,
  'itilde': 0x0129,
  'itildebelow': 0x1E2D,
  'iubopomofo': 0x3129,
  'iucyrillic': 0x044E,
  'ivowelsignbengali': 0x09BF,
  'ivowelsigndeva': 0x093F,
  'ivowelsigngujarati': 0x0ABF,
  'izhitsacyrillic': 0x0475,
  'izhitsadblgravecyrillic': 0x0477,
  'j': 0x006A,
  'jaarmenian': 0x0571,
  'jabengali': 0x099C,
  'jadeva': 0x091C,
  'jagujarati': 0x0A9C,
  'jagurmukhi': 0x0A1C,
  'jbopomofo': 0x3110,
  'jcaron': 0x01F0,
  'jcircle': 0x24D9,
  'jcircumflex': 0x0135,
  'jcrossedtail': 0x029D,
  'jdotlessstroke': 0x025F,
  'jecyrillic': 0x0458,
  'jeemarabic': 0x062C,
  'jeemfinalarabic': 0xFE9E,
  'jeeminitialarabic': 0xFE9F,
  'jeemmedialarabic': 0xFEA0,
  'jeharabic': 0x0698,
  'jehfinalarabic': 0xFB8B,
  'jhabengali': 0x099D,
  'jhadeva': 0x091D,
  'jhagujarati': 0x0A9D,
  'jhagurmukhi': 0x0A1D,
  'jheharmenian': 0x057B,
  'jis': 0x3004,
  'jmonospace': 0xFF4A,
  'jparen': 0x24A5,
  'jsuperior': 0x02B2,
  'k': 0x006B,
  'kabashkircyrillic': 0x04A1,
  'kabengali': 0x0995,
  'kacute': 0x1E31,
  'kacyrillic': 0x043A,
  'kadescendercyrillic': 0x049B,
  'kadeva': 0x0915,
  'kaf': 0x05DB,
  'kafarabic': 0x0643,
  'kafdagesh': 0xFB3B,
  'kafdageshhebrew': 0xFB3B,
  'kaffinalarabic': 0xFEDA,
  'kafhebrew': 0x05DB,
  'kafinitialarabic': 0xFEDB,
  'kafmedialarabic': 0xFEDC,
  'kafrafehebrew': 0xFB4D,
  'kagujarati': 0x0A95,
  'kagurmukhi': 0x0A15,
  'kahiragana': 0x304B,
  'kahookcyrillic': 0x04C4,
  'kakatakana': 0x30AB,
  'kakatakanahalfwidth': 0xFF76,
  'kappa': 0x03BA,
  'kappasymbolgreek': 0x03F0,
  'kapyeounmieumkorean': 0x3171,
  'kapyeounphieuphkorean': 0x3184,
  'kapyeounpieupkorean': 0x3178,
  'kapyeounssangpieupkorean': 0x3179,
  'karoriisquare': 0x330D,
  'kashidaautoarabic': 0x0640,
  'kashidaautonosidebearingarabic': 0x0640,
  'kasmallkatakana': 0x30F5,
  'kasquare': 0x3384,
  'kasraarabic': 0x0650,
  'kasratanarabic': 0x064D,
  'kastrokecyrillic': 0x049F,
  'katahiraprolongmarkhalfwidth': 0xFF70,
  'kaverticalstrokecyrillic': 0x049D,
  'kbopomofo': 0x310E,
  'kcalsquare': 0x3389,
  'kcaron': 0x01E9,
  'kcedilla': 0x0137,
  'kcircle': 0x24DA,
  'kcommaaccent': 0x0137,
  'kdotbelow': 0x1E33,
  'keharmenian': 0x0584,
  'kehiragana': 0x3051,
  'kekatakana': 0x30B1,
  'kekatakanahalfwidth': 0xFF79,
  'kenarmenian': 0x056F,
  'kesmallkatakana': 0x30F6,
  'kgreenlandic': 0x0138,
  'khabengali': 0x0996,
  'khacyrillic': 0x0445,
  'khadeva': 0x0916,
  'khagujarati': 0x0A96,
  'khagurmukhi': 0x0A16,
  'khaharabic': 0x062E,
  'khahfinalarabic': 0xFEA6,
  'khahinitialarabic': 0xFEA7,
  'khahmedialarabic': 0xFEA8,
  'kheicoptic': 0x03E7,
  'khhadeva': 0x0959,
  'khhagurmukhi': 0x0A59,
  'khieukhacirclekorean': 0x3278,
  'khieukhaparenkorean': 0x3218,
  'khieukhcirclekorean': 0x326A,
  'khieukhkorean': 0x314B,
  'khieukhparenkorean': 0x320A,
  'khokhaithai': 0x0E02,
  'khokhonthai': 0x0E05,
  'khokhuatthai': 0x0E03,
  'khokhwaithai': 0x0E04,
  'khomutthai': 0x0E5B,
  'khook': 0x0199,
  'khorakhangthai': 0x0E06,
  'khzsquare': 0x3391,
  'kihiragana': 0x304D,
  'kikatakana': 0x30AD,
  'kikatakanahalfwidth': 0xFF77,
  'kiroguramusquare': 0x3315,
  'kiromeetorusquare': 0x3316,
  'kirosquare': 0x3314,
  'kiyeokacirclekorean': 0x326E,
  'kiyeokaparenkorean': 0x320E,
  'kiyeokcirclekorean': 0x3260,
  'kiyeokkorean': 0x3131,
  'kiyeokparenkorean': 0x3200,
  'kiyeoksioskorean': 0x3133,
  'kjecyrillic': 0x045C,
  'klinebelow': 0x1E35,
  'klsquare': 0x3398,
  'kmcubedsquare': 0x33A6,
  'kmonospace': 0xFF4B,
  'kmsquaredsquare': 0x33A2,
  'kohiragana': 0x3053,
  'kohmsquare': 0x33C0,
  'kokaithai': 0x0E01,
  'kokatakana': 0x30B3,
  'kokatakanahalfwidth': 0xFF7A,
  'kooposquare': 0x331E,
  'koppacyrillic': 0x0481,
  'koreanstandardsymbol': 0x327F,
  'koroniscmb': 0x0343,
  'kparen': 0x24A6,
  'kpasquare': 0x33AA,
  'ksicyrillic': 0x046F,
  'ktsquare': 0x33CF,
  'kturned': 0x029E,
  'kuhiragana': 0x304F,
  'kukatakana': 0x30AF,
  'kukatakanahalfwidth': 0xFF78,
  'kvsquare': 0x33B8,
  'kwsquare': 0x33BE,
  'l': 0x006C,
  'labengali': 0x09B2,
  'lacute': 0x013A,
  'ladeva': 0x0932,
  'lagujarati': 0x0AB2,
  'lagurmukhi': 0x0A32,
  'lakkhangyaothai': 0x0E45,
  'lamaleffinalarabic': 0xFEFC,
  'lamalefhamzaabovefinalarabic': 0xFEF8,
  'lamalefhamzaaboveisolatedarabic': 0xFEF7,
  'lamalefhamzabelowfinalarabic': 0xFEFA,
  'lamalefhamzabelowisolatedarabic': 0xFEF9,
  'lamalefisolatedarabic': 0xFEFB,
  'lamalefmaddaabovefinalarabic': 0xFEF6,
  'lamalefmaddaaboveisolatedarabic': 0xFEF5,
  'lamarabic': 0x0644,
  'lambda': 0x03BB,
  'lambdastroke': 0x019B,
  'lamed': 0x05DC,
  'lameddagesh': 0xFB3C,
  'lameddageshhebrew': 0xFB3C,
  'lamedhebrew': 0x05DC,
  'lamfinalarabic': 0xFEDE,
  'lamhahinitialarabic': 0xFCCA,
  'laminitialarabic': 0xFEDF,
  'lamjeeminitialarabic': 0xFCC9,
  'lamkhahinitialarabic': 0xFCCB,
  'lamlamhehisolatedarabic': 0xFDF2,
  'lammedialarabic': 0xFEE0,
  'lammeemhahinitialarabic': 0xFD88,
  'lammeeminitialarabic': 0xFCCC,
  'largecircle': 0x25EF,
  'lbar': 0x019A,
  'lbelt': 0x026C,
  'lbopomofo': 0x310C,
  'lcaron': 0x013E,
  'lcedilla': 0x013C,
  'lcircle': 0x24DB,
  'lcircumflexbelow': 0x1E3D,
  'lcommaaccent': 0x013C,
  'ldot': 0x0140,
  'ldotaccent': 0x0140,
  'ldotbelow': 0x1E37,
  'ldotbelowmacron': 0x1E39,
  'leftangleabovecmb': 0x031A,
  'lefttackbelowcmb': 0x0318,
  'less': 0x003C,
  'lessequal': 0x2264,
  'lessequalorgreater': 0x22DA,
  'lessmonospace': 0xFF1C,
  'lessorequivalent': 0x2272,
  'lessorgreater': 0x2276,
  'lessoverequal': 0x2266,
  'lesssmall': 0xFE64,
  'lezh': 0x026E,
  'lfblock': 0x258C,
  'lhookretroflex': 0x026D,
  'lira': 0x20A4,
  'liwnarmenian': 0x056C,
  'lj': 0x01C9,
  'ljecyrillic': 0x0459,
  'll': 0xF6C0,
  'lladeva': 0x0933,
  'llagujarati': 0x0AB3,
  'llinebelow': 0x1E3B,
  'llladeva': 0x0934,
  'llvocalicbengali': 0x09E1,
  'llvocalicdeva': 0x0961,
  'llvocalicvowelsignbengali': 0x09E3,
  'llvocalicvowelsigndeva': 0x0963,
  'lmiddletilde': 0x026B,
  'lmonospace': 0xFF4C,
  'lmsquare': 0x33D0,
  'lochulathai': 0x0E2C,
  'logicaland': 0x2227,
  'logicalnot': 0x00AC,
  'logicalnotreversed': 0x2310,
  'logicalor': 0x2228,
  'lolingthai': 0x0E25,
  'longs': 0x017F,
  'lowlinecenterline': 0xFE4E,
  'lowlinecmb': 0x0332,
  'lowlinedashed': 0xFE4D,
  'lozenge': 0x25CA,
  'lparen': 0x24A7,
  'lslash': 0x0142,
  'lsquare': 0x2113,
  'lsuperior': 0xF6EE,
  'ltshade': 0x2591,
  'luthai': 0x0E26,
  'lvocalicbengali': 0x098C,
  'lvocalicdeva': 0x090C,
  'lvocalicvowelsignbengali': 0x09E2,
  'lvocalicvowelsigndeva': 0x0962,
  'lxsquare': 0x33D3,
  'm': 0x006D,
  'mabengali': 0x09AE,
  'macron': 0x00AF,
  'macronbelowcmb': 0x0331,
  'macroncmb': 0x0304,
  'macronlowmod': 0x02CD,
  'macronmonospace': 0xFFE3,
  'macute': 0x1E3F,
  'madeva': 0x092E,
  'magujarati': 0x0AAE,
  'magurmukhi': 0x0A2E,
  'mahapakhhebrew': 0x05A4,
  'mahapakhlefthebrew': 0x05A4,
  'mahiragana': 0x307E,
  'maichattawalowleftthai': 0xF895,
  'maichattawalowrightthai': 0xF894,
  'maichattawathai': 0x0E4B,
  'maichattawaupperleftthai': 0xF893,
  'maieklowleftthai': 0xF88C,
  'maieklowrightthai': 0xF88B,
  'maiekthai': 0x0E48,
  'maiekupperleftthai': 0xF88A,
  'maihanakatleftthai': 0xF884,
  'maihanakatthai': 0x0E31,
  'maitaikhuleftthai': 0xF889,
  'maitaikhuthai': 0x0E47,
  'maitholowleftthai': 0xF88F,
  'maitholowrightthai': 0xF88E,
  'maithothai': 0x0E49,
  'maithoupperleftthai': 0xF88D,
  'maitrilowleftthai': 0xF892,
  'maitrilowrightthai': 0xF891,
  'maitrithai': 0x0E4A,
  'maitriupperleftthai': 0xF890,
  'maiyamokthai': 0x0E46,
  'makatakana': 0x30DE,
  'makatakanahalfwidth': 0xFF8F,
  'male': 0x2642,
  'mansyonsquare': 0x3347,
  'maqafhebrew': 0x05BE,
  'mars': 0x2642,
  'masoracirclehebrew': 0x05AF,
  'masquare': 0x3383,
  'mbopomofo': 0x3107,
  'mbsquare': 0x33D4,
  'mcircle': 0x24DC,
  'mcubedsquare': 0x33A5,
  'mdotaccent': 0x1E41,
  'mdotbelow': 0x1E43,
  'meemarabic': 0x0645,
  'meemfinalarabic': 0xFEE2,
  'meeminitialarabic': 0xFEE3,
  'meemmedialarabic': 0xFEE4,
  'meemmeeminitialarabic': 0xFCD1,
  'meemmeemisolatedarabic': 0xFC48,
  'meetorusquare': 0x334D,
  'mehiragana': 0x3081,
  'meizierasquare': 0x337E,
  'mekatakana': 0x30E1,
  'mekatakanahalfwidth': 0xFF92,
  'mem': 0x05DE,
  'memdagesh': 0xFB3E,
  'memdageshhebrew': 0xFB3E,
  'memhebrew': 0x05DE,
  'menarmenian': 0x0574,
  'merkhahebrew': 0x05A5,
  'merkhakefulahebrew': 0x05A6,
  'merkhakefulalefthebrew': 0x05A6,
  'merkhalefthebrew': 0x05A5,
  'mhook': 0x0271,
  'mhzsquare': 0x3392,
  'middledotkatakanahalfwidth': 0xFF65,
  'middot': 0x00B7,
  'mieumacirclekorean': 0x3272,
  'mieumaparenkorean': 0x3212,
  'mieumcirclekorean': 0x3264,
  'mieumkorean': 0x3141,
  'mieumpansioskorean': 0x3170,
  'mieumparenkorean': 0x3204,
  'mieumpieupkorean': 0x316E,
  'mieumsioskorean': 0x316F,
  'mihiragana': 0x307F,
  'mikatakana': 0x30DF,
  'mikatakanahalfwidth': 0xFF90,
  'minus': 0x2212,
  'minusbelowcmb': 0x0320,
  'minuscircle': 0x2296,
  'minusmod': 0x02D7,
  'minusplus': 0x2213,
  'minute': 0x2032,
  'miribaarusquare': 0x334A,
  'mirisquare': 0x3349,
  'mlonglegturned': 0x0270,
  'mlsquare': 0x3396,
  'mmcubedsquare': 0x33A3,
  'mmonospace': 0xFF4D,
  'mmsquaredsquare': 0x339F,
  'mohiragana': 0x3082,
  'mohmsquare': 0x33C1,
  'mokatakana': 0x30E2,
  'mokatakanahalfwidth': 0xFF93,
  'molsquare': 0x33D6,
  'momathai': 0x0E21,
  'moverssquare': 0x33A7,
  'moverssquaredsquare': 0x33A8,
  'mparen': 0x24A8,
  'mpasquare': 0x33AB,
  'mssquare': 0x33B3,
  'msuperior': 0xF6EF,
  'mturned': 0x026F,
  'mu': 0x00B5,
  'mu1': 0x00B5,
  'muasquare': 0x3382,
  'muchgreater': 0x226B,
  'muchless': 0x226A,
  'mufsquare': 0x338C,
  'mugreek': 0x03BC,
  'mugsquare': 0x338D,
  'muhiragana': 0x3080,
  'mukatakana': 0x30E0,
  'mukatakanahalfwidth': 0xFF91,
  'mulsquare': 0x3395,
  'multiply': 0x00D7,
  'mumsquare': 0x339B,
  'munahhebrew': 0x05A3,
  'munahlefthebrew': 0x05A3,
  'musicalnote': 0x266A,
  'musicalnotedbl': 0x266B,
  'musicflatsign': 0x266D,
  'musicsharpsign': 0x266F,
  'mussquare': 0x33B2,
  'muvsquare': 0x33B6,
  'muwsquare': 0x33BC,
  'mvmegasquare': 0x33B9,
  'mvsquare': 0x33B7,
  'mwmegasquare': 0x33BF,
  'mwsquare': 0x33BD,
  'n': 0x006E,
  'nabengali': 0x09A8,
  'nabla': 0x2207,
  'nacute': 0x0144,
  'nadeva': 0x0928,
  'nagujarati': 0x0AA8,
  'nagurmukhi': 0x0A28,
  'nahiragana': 0x306A,
  'nakatakana': 0x30CA,
  'nakatakanahalfwidth': 0xFF85,
  'napostrophe': 0x0149,
  'nasquare': 0x3381,
  'nbopomofo': 0x310B,
  'nbspace': 0x00A0,
  'ncaron': 0x0148,
  'ncedilla': 0x0146,
  'ncircle': 0x24DD,
  'ncircumflexbelow': 0x1E4B,
  'ncommaaccent': 0x0146,
  'ndotaccent': 0x1E45,
  'ndotbelow': 0x1E47,
  'nehiragana': 0x306D,
  'nekatakana': 0x30CD,
  'nekatakanahalfwidth': 0xFF88,
  'newsheqelsign': 0x20AA,
  'nfsquare': 0x338B,
  'ngabengali': 0x0999,
  'ngadeva': 0x0919,
  'ngagujarati': 0x0A99,
  'ngagurmukhi': 0x0A19,
  'ngonguthai': 0x0E07,
  'nhiragana': 0x3093,
  'nhookleft': 0x0272,
  'nhookretroflex': 0x0273,
  'nieunacirclekorean': 0x326F,
  'nieunaparenkorean': 0x320F,
  'nieuncieuckorean': 0x3135,
  'nieuncirclekorean': 0x3261,
  'nieunhieuhkorean': 0x3136,
  'nieunkorean': 0x3134,
  'nieunpansioskorean': 0x3168,
  'nieunparenkorean': 0x3201,
  'nieunsioskorean': 0x3167,
  'nieuntikeutkorean': 0x3166,
  'nihiragana': 0x306B,
  'nikatakana': 0x30CB,
  'nikatakanahalfwidth': 0xFF86,
  'nikhahitleftthai': 0xF899,
  'nikhahitthai': 0x0E4D,
  'nine': 0x0039,
  'ninearabic': 0x0669,
  'ninebengali': 0x09EF,
  'ninecircle': 0x2468,
  'ninecircleinversesansserif': 0x2792,
  'ninedeva': 0x096F,
  'ninegujarati': 0x0AEF,
  'ninegurmukhi': 0x0A6F,
  'ninehackarabic': 0x0669,
  'ninehangzhou': 0x3029,
  'nineideographicparen': 0x3228,
  'nineinferior': 0x2089,
  'ninemonospace': 0xFF19,
  'nineoldstyle': 0xF739,
  'nineparen': 0x247C,
  'nineperiod': 0x2490,
  'ninepersian': 0x06F9,
  'nineroman': 0x2178,
  'ninesuperior': 0x2079,
  'nineteencircle': 0x2472,
  'nineteenparen': 0x2486,
  'nineteenperiod': 0x249A,
  'ninethai': 0x0E59,
  'nj': 0x01CC,
  'njecyrillic': 0x045A,
  'nkatakana': 0x30F3,
  'nkatakanahalfwidth': 0xFF9D,
  'nlegrightlong': 0x019E,
  'nlinebelow': 0x1E49,
  'nmonospace': 0xFF4E,
  'nmsquare': 0x339A,
  'nnabengali': 0x09A3,
  'nnadeva': 0x0923,
  'nnagujarati': 0x0AA3,
  'nnagurmukhi': 0x0A23,
  'nnnadeva': 0x0929,
  'nohiragana': 0x306E,
  'nokatakana': 0x30CE,
  'nokatakanahalfwidth': 0xFF89,
  'nonbreakingspace': 0x00A0,
  'nonenthai': 0x0E13,
  'nonuthai': 0x0E19,
  'noonarabic': 0x0646,
  'noonfinalarabic': 0xFEE6,
  'noonghunnaarabic': 0x06BA,
  'noonghunnafinalarabic': 0xFB9F,
  'nooninitialarabic': 0xFEE7,
  'noonjeeminitialarabic': 0xFCD2,
  'noonjeemisolatedarabic': 0xFC4B,
  'noonmedialarabic': 0xFEE8,
  'noonmeeminitialarabic': 0xFCD5,
  'noonmeemisolatedarabic': 0xFC4E,
  'noonnoonfinalarabic': 0xFC8D,
  'notcontains': 0x220C,
  'notelement': 0x2209,
  'notelementof': 0x2209,
  'notequal': 0x2260,
  'notgreater': 0x226F,
  'notgreaternorequal': 0x2271,
  'notgreaternorless': 0x2279,
  'notidentical': 0x2262,
  'notless': 0x226E,
  'notlessnorequal': 0x2270,
  'notparallel': 0x2226,
  'notprecedes': 0x2280,
  'notsubset': 0x2284,
  'notsucceeds': 0x2281,
  'notsuperset': 0x2285,
  'nowarmenian': 0x0576,
  'nparen': 0x24A9,
  'nssquare': 0x33B1,
  'nsuperior': 0x207F,
  'ntilde': 0x00F1,
  'nu': 0x03BD,
  'nuhiragana': 0x306C,
  'nukatakana': 0x30CC,
  'nukatakanahalfwidth': 0xFF87,
  'nuktabengali': 0x09BC,
  'nuktadeva': 0x093C,
  'nuktagujarati': 0x0ABC,
  'nuktagurmukhi': 0x0A3C,
  'numbersign': 0x0023,
  'numbersignmonospace': 0xFF03,
  'numbersignsmall': 0xFE5F,
  'numeralsigngreek': 0x0374,
  'numeralsignlowergreek': 0x0375,
  'numero': 0x2116,
  'nun': 0x05E0,
  'nundagesh': 0xFB40,
  'nundageshhebrew': 0xFB40,
  'nunhebrew': 0x05E0,
  'nvsquare': 0x33B5,
  'nwsquare': 0x33BB,
  'nyabengali': 0x099E,
  'nyadeva': 0x091E,
  'nyagujarati': 0x0A9E,
  'nyagurmukhi': 0x0A1E,
  'o': 0x006F,
  'oacute': 0x00F3,
  'oangthai': 0x0E2D,
  'obarred': 0x0275,
  'obarredcyrillic': 0x04E9,
  'obarreddieresiscyrillic': 0x04EB,
  'obengali': 0x0993,
  'obopomofo': 0x311B,
  'obreve': 0x014F,
  'ocandradeva': 0x0911,
  'ocandragujarati': 0x0A91,
  'ocandravowelsigndeva': 0x0949,
  'ocandravowelsigngujarati': 0x0AC9,
  'ocaron': 0x01D2,
  'ocircle': 0x24DE,
  'ocircumflex': 0x00F4,
  'ocircumflexacute': 0x1ED1,
  'ocircumflexdotbelow': 0x1ED9,
  'ocircumflexgrave': 0x1ED3,
  'ocircumflexhookabove': 0x1ED5,
  'ocircumflextilde': 0x1ED7,
  'ocyrillic': 0x043E,
  'odblacute': 0x0151,
  'odblgrave': 0x020D,
  'odeva': 0x0913,
  'odieresis': 0x00F6,
  'odieresiscyrillic': 0x04E7,
  'odotbelow': 0x1ECD,
  'oe': 0x0153,
  'oekorean': 0x315A,
  'ogonek': 0x02DB,
  'ogonekcmb': 0x0328,
  'ograve': 0x00F2,
  'ogujarati': 0x0A93,
  'oharmenian': 0x0585,
  'ohiragana': 0x304A,
  'ohookabove': 0x1ECF,
  'ohorn': 0x01A1,
  'ohornacute': 0x1EDB,
  'ohorndotbelow': 0x1EE3,
  'ohorngrave': 0x1EDD,
  'ohornhookabove': 0x1EDF,
  'ohorntilde': 0x1EE1,
  'ohungarumlaut': 0x0151,
  'oi': 0x01A3,
  'oinvertedbreve': 0x020F,
  'okatakana': 0x30AA,
  'okatakanahalfwidth': 0xFF75,
  'okorean': 0x3157,
  'olehebrew': 0x05AB,
  'omacron': 0x014D,
  'omacronacute': 0x1E53,
  'omacrongrave': 0x1E51,
  'omdeva': 0x0950,
  'omega': 0x03C9,
  'omega1': 0x03D6,
  'omegacyrillic': 0x0461,
  'omegalatinclosed': 0x0277,
  'omegaroundcyrillic': 0x047B,
  'omegatitlocyrillic': 0x047D,
  'omegatonos': 0x03CE,
  'omgujarati': 0x0AD0,
  'omicron': 0x03BF,
  'omicrontonos': 0x03CC,
  'omonospace': 0xFF4F,
  'one': 0x0031,
  'onearabic': 0x0661,
  'onebengali': 0x09E7,
  'onecircle': 0x2460,
  'onecircleinversesansserif': 0x278A,
  'onedeva': 0x0967,
  'onedotenleader': 0x2024,
  'oneeighth': 0x215B,
  'onefitted': 0xF6DC,
  'onegujarati': 0x0AE7,
  'onegurmukhi': 0x0A67,
  'onehackarabic': 0x0661,
  'onehalf': 0x00BD,
  'onehangzhou': 0x3021,
  'oneideographicparen': 0x3220,
  'oneinferior': 0x2081,
  'onemonospace': 0xFF11,
  'onenumeratorbengali': 0x09F4,
  'oneoldstyle': 0xF731,
  'oneparen': 0x2474,
  'oneperiod': 0x2488,
  'onepersian': 0x06F1,
  'onequarter': 0x00BC,
  'oneroman': 0x2170,
  'onesuperior': 0x00B9,
  'onethai': 0x0E51,
  'onethird': 0x2153,
  'oogonek': 0x01EB,
  'oogonekmacron': 0x01ED,
  'oogurmukhi': 0x0A13,
  'oomatragurmukhi': 0x0A4B,
  'oopen': 0x0254,
  'oparen': 0x24AA,
  'openbullet': 0x25E6,
  'option': 0x2325,
  'ordfeminine': 0x00AA,
  'ordmasculine': 0x00BA,
  'orthogonal': 0x221F,
  'oshortdeva': 0x0912,
  'oshortvowelsigndeva': 0x094A,
  'oslash': 0x00F8,
  'oslashacute': 0x01FF,
  'osmallhiragana': 0x3049,
  'osmallkatakana': 0x30A9,
  'osmallkatakanahalfwidth': 0xFF6B,
  'ostrokeacute': 0x01FF,
  'osuperior': 0xF6F0,
  'otcyrillic': 0x047F,
  'otilde': 0x00F5,
  'otildeacute': 0x1E4D,
  'otildedieresis': 0x1E4F,
  'oubopomofo': 0x3121,
  'overline': 0x203E,
  'overlinecenterline': 0xFE4A,
  'overlinecmb': 0x0305,
  'overlinedashed': 0xFE49,
  'overlinedblwavy': 0xFE4C,
  'overlinewavy': 0xFE4B,
  'overscore': 0x00AF,
  'ovowelsignbengali': 0x09CB,
  'ovowelsigndeva': 0x094B,
  'ovowelsigngujarati': 0x0ACB,
  'p': 0x0070,
  'paampssquare': 0x3380,
  'paasentosquare': 0x332B,
  'pabengali': 0x09AA,
  'pacute': 0x1E55,
  'padeva': 0x092A,
  'pagedown': 0x21DF,
  'pageup': 0x21DE,
  'pagujarati': 0x0AAA,
  'pagurmukhi': 0x0A2A,
  'pahiragana': 0x3071,
  'paiyannoithai': 0x0E2F,
  'pakatakana': 0x30D1,
  'palatalizationcyrilliccmb': 0x0484,
  'palochkacyrillic': 0x04C0,
  'pansioskorean': 0x317F,
  'paragraph': 0x00B6,
  'parallel': 0x2225,
  'parenleft': 0x0028,
  'parenleftaltonearabic': 0xFD3E,
  'parenleftbt': 0xF8ED,
  'parenleftex': 0xF8EC,
  'parenleftinferior': 0x208D,
  'parenleftmonospace': 0xFF08,
  'parenleftsmall': 0xFE59,
  'parenleftsuperior': 0x207D,
  'parenlefttp': 0xF8EB,
  'parenleftvertical': 0xFE35,
  'parenright': 0x0029,
  'parenrightaltonearabic': 0xFD3F,
  'parenrightbt': 0xF8F8,
  'parenrightex': 0xF8F7,
  'parenrightinferior': 0x208E,
  'parenrightmonospace': 0xFF09,
  'parenrightsmall': 0xFE5A,
  'parenrightsuperior': 0x207E,
  'parenrighttp': 0xF8F6,
  'parenrightvertical': 0xFE36,
  'partialdiff': 0x2202,
  'paseqhebrew': 0x05C0,
  'pashtahebrew': 0x0599,
  'pasquare': 0x33A9,
  'patah': 0x05B7,
  'patah11': 0x05B7,
  'patah1d': 0x05B7,
  'patah2a': 0x05B7,
  'patahhebrew': 0x05B7,
  'patahnarrowhebrew': 0x05B7,
  'patahquarterhebrew': 0x05B7,
  'patahwidehebrew': 0x05B7,
  'pazerhebrew': 0x05A1,
  'pbopomofo': 0x3106,
  'pcircle': 0x24DF,
  'pdotaccent': 0x1E57,
  'pe': 0x05E4,
  'pecyrillic': 0x043F,
  'pedagesh': 0xFB44,
  'pedageshhebrew': 0xFB44,
  'peezisquare': 0x333B,
  'pefinaldageshhebrew': 0xFB43,
  'peharabic': 0x067E,
  'peharmenian': 0x057A,
  'pehebrew': 0x05E4,
  'pehfinalarabic': 0xFB57,
  'pehinitialarabic': 0xFB58,
  'pehiragana': 0x307A,
  'pehmedialarabic': 0xFB59,
  'pekatakana': 0x30DA,
  'pemiddlehookcyrillic': 0x04A7,
  'perafehebrew': 0xFB4E,
  'percent': 0x0025,
  'percentarabic': 0x066A,
  'percentmonospace': 0xFF05,
  'percentsmall': 0xFE6A,
  'period': 0x002E,
  'periodarmenian': 0x0589,
  'periodcentered': 0x00B7,
  'periodhalfwidth': 0xFF61,
  'periodinferior': 0xF6E7,
  'periodmonospace': 0xFF0E,
  'periodsmall': 0xFE52,
  'periodsuperior': 0xF6E8,
  'perispomenigreekcmb': 0x0342,
  'perpendicular': 0x22A5,
  'perthousand': 0x2030,
  'peseta': 0x20A7,
  'pfsquare': 0x338A,
  'phabengali': 0x09AB,
  'phadeva': 0x092B,
  'phagujarati': 0x0AAB,
  'phagurmukhi': 0x0A2B,
  'phi': 0x03C6,
  'phi1': 0x03D5,
  'phieuphacirclekorean': 0x327A,
  'phieuphaparenkorean': 0x321A,
  'phieuphcirclekorean': 0x326C,
  'phieuphkorean': 0x314D,
  'phieuphparenkorean': 0x320C,
  'philatin': 0x0278,
  'phinthuthai': 0x0E3A,
  'phisymbolgreek': 0x03D5,
  'phook': 0x01A5,
  'phophanthai': 0x0E1E,
  'phophungthai': 0x0E1C,
  'phosamphaothai': 0x0E20,
  'pi': 0x03C0,
  'pieupacirclekorean': 0x3273,
  'pieupaparenkorean': 0x3213,
  'pieupcieuckorean': 0x3176,
  'pieupcirclekorean': 0x3265,
  'pieupkiyeokkorean': 0x3172,
  'pieupkorean': 0x3142,
  'pieupparenkorean': 0x3205,
  'pieupsioskiyeokkorean': 0x3174,
  'pieupsioskorean': 0x3144,
  'pieupsiostikeutkorean': 0x3175,
  'pieupthieuthkorean': 0x3177,
  'pieuptikeutkorean': 0x3173,
  'pihiragana': 0x3074,
  'pikatakana': 0x30D4,
  'pisymbolgreek': 0x03D6,
  'piwrarmenian': 0x0583,
  'plus': 0x002B,
  'plusbelowcmb': 0x031F,
  'pluscircle': 0x2295,
  'plusminus': 0x00B1,
  'plusmod': 0x02D6,
  'plusmonospace': 0xFF0B,
  'plussmall': 0xFE62,
  'plussuperior': 0x207A,
  'pmonospace': 0xFF50,
  'pmsquare': 0x33D8,
  'pohiragana': 0x307D,
  'pointingindexdownwhite': 0x261F,
  'pointingindexleftwhite': 0x261C,
  'pointingindexrightwhite': 0x261E,
  'pointingindexupwhite': 0x261D,
  'pokatakana': 0x30DD,
  'poplathai': 0x0E1B,
  'postalmark': 0x3012,
  'postalmarkface': 0x3020,
  'pparen': 0x24AB,
  'precedes': 0x227A,
  'prescription': 0x211E,
  'primemod': 0x02B9,
  'primereversed': 0x2035,
  'product': 0x220F,
  'projective': 0x2305,
  'prolongedkana': 0x30FC,
  'propellor': 0x2318,
  'propersubset': 0x2282,
  'propersuperset': 0x2283,
  'proportion': 0x2237,
  'proportional': 0x221D,
  'psi': 0x03C8,
  'psicyrillic': 0x0471,
  'psilipneumatacyrilliccmb': 0x0486,
  'pssquare': 0x33B0,
  'puhiragana': 0x3077,
  'pukatakana': 0x30D7,
  'pvsquare': 0x33B4,
  'pwsquare': 0x33BA,
  'q': 0x0071,
  'qadeva': 0x0958,
  'qadmahebrew': 0x05A8,
  'qafarabic': 0x0642,
  'qaffinalarabic': 0xFED6,
  'qafinitialarabic': 0xFED7,
  'qafmedialarabic': 0xFED8,
  'qamats': 0x05B8,
  'qamats10': 0x05B8,
  'qamats1a': 0x05B8,
  'qamats1c': 0x05B8,
  'qamats27': 0x05B8,
  'qamats29': 0x05B8,
  'qamats33': 0x05B8,
  'qamatsde': 0x05B8,
  'qamatshebrew': 0x05B8,
  'qamatsnarrowhebrew': 0x05B8,
  'qamatsqatanhebrew': 0x05B8,
  'qamatsqatannarrowhebrew': 0x05B8,
  'qamatsqatanquarterhebrew': 0x05B8,
  'qamatsqatanwidehebrew': 0x05B8,
  'qamatsquarterhebrew': 0x05B8,
  'qamatswidehebrew': 0x05B8,
  'qarneyparahebrew': 0x059F,
  'qbopomofo': 0x3111,
  'qcircle': 0x24E0,
  'qhook': 0x02A0,
  'qmonospace': 0xFF51,
  'qof': 0x05E7,
  'qofdagesh': 0xFB47,
  'qofdageshhebrew': 0xFB47,
  'qofhebrew': 0x05E7,
  'qparen': 0x24AC,
  'quarternote': 0x2669,
  'qubuts': 0x05BB,
  'qubuts18': 0x05BB,
  'qubuts25': 0x05BB,
  'qubuts31': 0x05BB,
  'qubutshebrew': 0x05BB,
  'qubutsnarrowhebrew': 0x05BB,
  'qubutsquarterhebrew': 0x05BB,
  'qubutswidehebrew': 0x05BB,
  'question': 0x003F,
  'questionarabic': 0x061F,
  'questionarmenian': 0x055E,
  'questiondown': 0x00BF,
  'questiondownsmall': 0xF7BF,
  'questiongreek': 0x037E,
  'questionmonospace': 0xFF1F,
  'questionsmall': 0xF73F,
  'quotedbl': 0x0022,
  'quotedblbase': 0x201E,
  'quotedblleft': 0x201C,
  'quotedblmonospace': 0xFF02,
  'quotedblprime': 0x301E,
  'quotedblprimereversed': 0x301D,
  'quotedblright': 0x201D,
  'quoteleft': 0x2018,
  'quoteleftreversed': 0x201B,
  'quotereversed': 0x201B,
  'quoteright': 0x2019,
  'quoterightn': 0x0149,
  'quotesinglbase': 0x201A,
  'quotesingle': 0x0027,
  'quotesinglemonospace': 0xFF07,
  'r': 0x0072,
  'raarmenian': 0x057C,
  'rabengali': 0x09B0,
  'racute': 0x0155,
  'radeva': 0x0930,
  'radical': 0x221A,
  'radicalex': 0xF8E5,
  'radoverssquare': 0x33AE,
  'radoverssquaredsquare': 0x33AF,
  'radsquare': 0x33AD,
  'rafe': 0x05BF,
  'rafehebrew': 0x05BF,
  'ragujarati': 0x0AB0,
  'ragurmukhi': 0x0A30,
  'rahiragana': 0x3089,
  'rakatakana': 0x30E9,
  'rakatakanahalfwidth': 0xFF97,
  'ralowerdiagonalbengali': 0x09F1,
  'ramiddlediagonalbengali': 0x09F0,
  'ramshorn': 0x0264,
  'ratio': 0x2236,
  'rbopomofo': 0x3116,
  'rcaron': 0x0159,
  'rcedilla': 0x0157,
  'rcircle': 0x24E1,
  'rcommaaccent': 0x0157,
  'rdblgrave': 0x0211,
  'rdotaccent': 0x1E59,
  'rdotbelow': 0x1E5B,
  'rdotbelowmacron': 0x1E5D,
  'referencemark': 0x203B,
  'reflexsubset': 0x2286,
  'reflexsuperset': 0x2287,
  'registered': 0x00AE,
  'registersans': 0xF8E8,
  'registerserif': 0xF6DA,
  'reharabic': 0x0631,
  'reharmenian': 0x0580,
  'rehfinalarabic': 0xFEAE,
  'rehiragana': 0x308C,
  'rekatakana': 0x30EC,
  'rekatakanahalfwidth': 0xFF9A,
  'resh': 0x05E8,
  'reshdageshhebrew': 0xFB48,
  'reshhebrew': 0x05E8,
  'reversedtilde': 0x223D,
  'reviahebrew': 0x0597,
  'reviamugrashhebrew': 0x0597,
  'revlogicalnot': 0x2310,
  'rfishhook': 0x027E,
  'rfishhookreversed': 0x027F,
  'rhabengali': 0x09DD,
  'rhadeva': 0x095D,
  'rho': 0x03C1,
  'rhook': 0x027D,
  'rhookturned': 0x027B,
  'rhookturnedsuperior': 0x02B5,
  'rhosymbolgreek': 0x03F1,
  'rhotichookmod': 0x02DE,
  'rieulacirclekorean': 0x3271,
  'rieulaparenkorean': 0x3211,
  'rieulcirclekorean': 0x3263,
  'rieulhieuhkorean': 0x3140,
  'rieulkiyeokkorean': 0x313A,
  'rieulkiyeoksioskorean': 0x3169,
  'rieulkorean': 0x3139,
  'rieulmieumkorean': 0x313B,
  'rieulpansioskorean': 0x316C,
  'rieulparenkorean': 0x3203,
  'rieulphieuphkorean': 0x313F,
  'rieulpieupkorean': 0x313C,
  'rieulpieupsioskorean': 0x316B,
  'rieulsioskorean': 0x313D,
  'rieulthieuthkorean': 0x313E,
  'rieultikeutkorean': 0x316A,
  'rieulyeorinhieuhkorean': 0x316D,
  'rightangle': 0x221F,
  'righttackbelowcmb': 0x0319,
  'righttriangle': 0x22BF,
  'rihiragana': 0x308A,
  'rikatakana': 0x30EA,
  'rikatakanahalfwidth': 0xFF98,
  'ring': 0x02DA,
  'ringbelowcmb': 0x0325,
  'ringcmb': 0x030A,
  'ringhalfleft': 0x02BF,
  'ringhalfleftarmenian': 0x0559,
  'ringhalfleftbelowcmb': 0x031C,
  'ringhalfleftcentered': 0x02D3,
  'ringhalfright': 0x02BE,
  'ringhalfrightbelowcmb': 0x0339,
  'ringhalfrightcentered': 0x02D2,
  'rinvertedbreve': 0x0213,
  'rittorusquare': 0x3351,
  'rlinebelow': 0x1E5F,
  'rlongleg': 0x027C,
  'rlonglegturned': 0x027A,
  'rmonospace': 0xFF52,
  'rohiragana': 0x308D,
  'rokatakana': 0x30ED,
  'rokatakanahalfwidth': 0xFF9B,
  'roruathai': 0x0E23,
  'rparen': 0x24AD,
  'rrabengali': 0x09DC,
  'rradeva': 0x0931,
  'rragurmukhi': 0x0A5C,
  'rreharabic': 0x0691,
  'rrehfinalarabic': 0xFB8D,
  'rrvocalicbengali': 0x09E0,
  'rrvocalicdeva': 0x0960,
  'rrvocalicgujarati': 0x0AE0,
  'rrvocalicvowelsignbengali': 0x09C4,
  'rrvocalicvowelsigndeva': 0x0944,
  'rrvocalicvowelsigngujarati': 0x0AC4,
  'rsuperior': 0xF6F1,
  'rtblock': 0x2590,
  'rturned': 0x0279,
  'rturnedsuperior': 0x02B4,
  'ruhiragana': 0x308B,
  'rukatakana': 0x30EB,
  'rukatakanahalfwidth': 0xFF99,
  'rupeemarkbengali': 0x09F2,
  'rupeesignbengali': 0x09F3,
  'rupiah': 0xF6DD,
  'ruthai': 0x0E24,
  'rvocalicbengali': 0x098B,
  'rvocalicdeva': 0x090B,
  'rvocalicgujarati': 0x0A8B,
  'rvocalicvowelsignbengali': 0x09C3,
  'rvocalicvowelsigndeva': 0x0943,
  'rvocalicvowelsigngujarati': 0x0AC3,
  's': 0x0073,
  'sabengali': 0x09B8,
  'sacute': 0x015B,
  'sacutedotaccent': 0x1E65,
  'sadarabic': 0x0635,
  'sadeva': 0x0938,
  'sadfinalarabic': 0xFEBA,
  'sadinitialarabic': 0xFEBB,
  'sadmedialarabic': 0xFEBC,
  'sagujarati': 0x0AB8,
  'sagurmukhi': 0x0A38,
  'sahiragana': 0x3055,
  'sakatakana': 0x30B5,
  'sakatakanahalfwidth': 0xFF7B,
  'sallallahoualayhewasallamarabic': 0xFDFA,
  'samekh': 0x05E1,
  'samekhdagesh': 0xFB41,
  'samekhdageshhebrew': 0xFB41,
  'samekhhebrew': 0x05E1,
  'saraaathai': 0x0E32,
  'saraaethai': 0x0E41,
  'saraaimaimalaithai': 0x0E44,
  'saraaimaimuanthai': 0x0E43,
  'saraamthai': 0x0E33,
  'saraathai': 0x0E30,
  'saraethai': 0x0E40,
  'saraiileftthai': 0xF886,
  'saraiithai': 0x0E35,
  'saraileftthai': 0xF885,
  'saraithai': 0x0E34,
  'saraothai': 0x0E42,
  'saraueeleftthai': 0xF888,
  'saraueethai': 0x0E37,
  'saraueleftthai': 0xF887,
  'sarauethai': 0x0E36,
  'sarauthai': 0x0E38,
  'sarauuthai': 0x0E39,
  'sbopomofo': 0x3119,
  'scaron': 0x0161,
  'scarondotaccent': 0x1E67,
  'scedilla': 0x015F,
  'schwa': 0x0259,
  'schwacyrillic': 0x04D9,
  'schwadieresiscyrillic': 0x04DB,
  'schwahook': 0x025A,
  'scircle': 0x24E2,
  'scircumflex': 0x015D,
  'scommaaccent': 0x0219,
  'sdotaccent': 0x1E61,
  'sdotbelow': 0x1E63,
  'sdotbelowdotaccent': 0x1E69,
  'seagullbelowcmb': 0x033C,
  'second': 0x2033,
  'secondtonechinese': 0x02CA,
  'section': 0x00A7,
  'seenarabic': 0x0633,
  'seenfinalarabic': 0xFEB2,
  'seeninitialarabic': 0xFEB3,
  'seenmedialarabic': 0xFEB4,
  'segol': 0x05B6,
  'segol13': 0x05B6,
  'segol1f': 0x05B6,
  'segol2c': 0x05B6,
  'segolhebrew': 0x05B6,
  'segolnarrowhebrew': 0x05B6,
  'segolquarterhebrew': 0x05B6,
  'segoltahebrew': 0x0592,
  'segolwidehebrew': 0x05B6,
  'seharmenian': 0x057D,
  'sehiragana': 0x305B,
  'sekatakana': 0x30BB,
  'sekatakanahalfwidth': 0xFF7E,
  'semicolon': 0x003B,
  'semicolonarabic': 0x061B,
  'semicolonmonospace': 0xFF1B,
  'semicolonsmall': 0xFE54,
  'semivoicedmarkkana': 0x309C,
  'semivoicedmarkkanahalfwidth': 0xFF9F,
  'sentisquare': 0x3322,
  'sentosquare': 0x3323,
  'seven': 0x0037,
  'sevenarabic': 0x0667,
  'sevenbengali': 0x09ED,
  'sevencircle': 0x2466,
  'sevencircleinversesansserif': 0x2790,
  'sevendeva': 0x096D,
  'seveneighths': 0x215E,
  'sevengujarati': 0x0AED,
  'sevengurmukhi': 0x0A6D,
  'sevenhackarabic': 0x0667,
  'sevenhangzhou': 0x3027,
  'sevenideographicparen': 0x3226,
  'seveninferior': 0x2087,
  'sevenmonospace': 0xFF17,
  'sevenoldstyle': 0xF737,
  'sevenparen': 0x247A,
  'sevenperiod': 0x248E,
  'sevenpersian': 0x06F7,
  'sevenroman': 0x2176,
  'sevensuperior': 0x2077,
  'seventeencircle': 0x2470,
  'seventeenparen': 0x2484,
  'seventeenperiod': 0x2498,
  'seventhai': 0x0E57,
  'sfthyphen': 0x00AD,
  'shaarmenian': 0x0577,
  'shabengali': 0x09B6,
  'shacyrillic': 0x0448,
  'shaddaarabic': 0x0651,
  'shaddadammaarabic': 0xFC61,
  'shaddadammatanarabic': 0xFC5E,
  'shaddafathaarabic': 0xFC60,
  'shaddakasraarabic': 0xFC62,
  'shaddakasratanarabic': 0xFC5F,
  'shade': 0x2592,
  'shadedark': 0x2593,
  'shadelight': 0x2591,
  'shademedium': 0x2592,
  'shadeva': 0x0936,
  'shagujarati': 0x0AB6,
  'shagurmukhi': 0x0A36,
  'shalshelethebrew': 0x0593,
  'shbopomofo': 0x3115,
  'shchacyrillic': 0x0449,
  'sheenarabic': 0x0634,
  'sheenfinalarabic': 0xFEB6,
  'sheeninitialarabic': 0xFEB7,
  'sheenmedialarabic': 0xFEB8,
  'sheicoptic': 0x03E3,
  'sheqel': 0x20AA,
  'sheqelhebrew': 0x20AA,
  'sheva': 0x05B0,
  'sheva115': 0x05B0,
  'sheva15': 0x05B0,
  'sheva22': 0x05B0,
  'sheva2e': 0x05B0,
  'shevahebrew': 0x05B0,
  'shevanarrowhebrew': 0x05B0,
  'shevaquarterhebrew': 0x05B0,
  'shevawidehebrew': 0x05B0,
  'shhacyrillic': 0x04BB,
  'shimacoptic': 0x03ED,
  'shin': 0x05E9,
  'shindagesh': 0xFB49,
  'shindageshhebrew': 0xFB49,
  'shindageshshindot': 0xFB2C,
  'shindageshshindothebrew': 0xFB2C,
  'shindageshsindot': 0xFB2D,
  'shindageshsindothebrew': 0xFB2D,
  'shindothebrew': 0x05C1,
  'shinhebrew': 0x05E9,
  'shinshindot': 0xFB2A,
  'shinshindothebrew': 0xFB2A,
  'shinsindot': 0xFB2B,
  'shinsindothebrew': 0xFB2B,
  'shook': 0x0282,
  'sigma': 0x03C3,
  'sigma1': 0x03C2,
  'sigmafinal': 0x03C2,
  'sigmalunatesymbolgreek': 0x03F2,
  'sihiragana': 0x3057,
  'sikatakana': 0x30B7,
  'sikatakanahalfwidth': 0xFF7C,
  'siluqhebrew': 0x05BD,
  'siluqlefthebrew': 0x05BD,
  'similar': 0x223C,
  'sindothebrew': 0x05C2,
  'siosacirclekorean': 0x3274,
  'siosaparenkorean': 0x3214,
  'sioscieuckorean': 0x317E,
  'sioscirclekorean': 0x3266,
  'sioskiyeokkorean': 0x317A,
  'sioskorean': 0x3145,
  'siosnieunkorean': 0x317B,
  'siosparenkorean': 0x3206,
  'siospieupkorean': 0x317D,
  'siostikeutkorean': 0x317C,
  'six': 0x0036,
  'sixarabic': 0x0666,
  'sixbengali': 0x09EC,
  'sixcircle': 0x2465,
  'sixcircleinversesansserif': 0x278F,
  'sixdeva': 0x096C,
  'sixgujarati': 0x0AEC,
  'sixgurmukhi': 0x0A6C,
  'sixhackarabic': 0x0666,
  'sixhangzhou': 0x3026,
  'sixideographicparen': 0x3225,
  'sixinferior': 0x2086,
  'sixmonospace': 0xFF16,
  'sixoldstyle': 0xF736,
  'sixparen': 0x2479,
  'sixperiod': 0x248D,
  'sixpersian': 0x06F6,
  'sixroman': 0x2175,
  'sixsuperior': 0x2076,
  'sixteencircle': 0x246F,
  'sixteencurrencydenominatorbengali': 0x09F9,
  'sixteenparen': 0x2483,
  'sixteenperiod': 0x2497,
  'sixthai': 0x0E56,
  'slash': 0x002F,
  'slashmonospace': 0xFF0F,
  'slong': 0x017F,
  'slongdotaccent': 0x1E9B,
  'smileface': 0x263A,
  'smonospace': 0xFF53,
  'sofpasuqhebrew': 0x05C3,
  'softhyphen': 0x00AD,
  'softsigncyrillic': 0x044C,
  'sohiragana': 0x305D,
  'sokatakana': 0x30BD,
  'sokatakanahalfwidth': 0xFF7F,
  'soliduslongoverlaycmb': 0x0338,
  'solidusshortoverlaycmb': 0x0337,
  'sorusithai': 0x0E29,
  'sosalathai': 0x0E28,
  'sosothai': 0x0E0B,
  'sosuathai': 0x0E2A,
  'space': 0x0020,
  'spacehackarabic': 0x0020,
  'spade': 0x2660,
  'spadesuitblack': 0x2660,
  'spadesuitwhite': 0x2664,
  'sparen': 0x24AE,
  'squarebelowcmb': 0x033B,
  'squarecc': 0x33C4,
  'squarecm': 0x339D,
  'squarediagonalcrosshatchfill': 0x25A9,
  'squarehorizontalfill': 0x25A4,
  'squarekg': 0x338F,
  'squarekm': 0x339E,
  'squarekmcapital': 0x33CE,
  'squareln': 0x33D1,
  'squarelog': 0x33D2,
  'squaremg': 0x338E,
  'squaremil': 0x33D5,
  'squaremm': 0x339C,
  'squaremsquared': 0x33A1,
  'squareorthogonalcrosshatchfill': 0x25A6,
  'squareupperlefttolowerrightfill': 0x25A7,
  'squareupperrighttolowerleftfill': 0x25A8,
  'squareverticalfill': 0x25A5,
  'squarewhitewithsmallblack': 0x25A3,
  'srsquare': 0x33DB,
  'ssabengali': 0x09B7,
  'ssadeva': 0x0937,
  'ssagujarati': 0x0AB7,
  'ssangcieuckorean': 0x3149,
  'ssanghieuhkorean': 0x3185,
  'ssangieungkorean': 0x3180,
  'ssangkiyeokkorean': 0x3132,
  'ssangnieunkorean': 0x3165,
  'ssangpieupkorean': 0x3143,
  'ssangsioskorean': 0x3146,
  'ssangtikeutkorean': 0x3138,
  'ssuperior': 0xF6F2,
  'sterling': 0x00A3,
  'sterlingmonospace': 0xFFE1,
  'strokelongoverlaycmb': 0x0336,
  'strokeshortoverlaycmb': 0x0335,
  'subset': 0x2282,
  'subsetnotequal': 0x228A,
  'subsetorequal': 0x2286,
  'succeeds': 0x227B,
  'suchthat': 0x220B,
  'suhiragana': 0x3059,
  'sukatakana': 0x30B9,
  'sukatakanahalfwidth': 0xFF7D,
  'sukunarabic': 0x0652,
  'summation': 0x2211,
  'sun': 0x263C,
  'superset': 0x2283,
  'supersetnotequal': 0x228B,
  'supersetorequal': 0x2287,
  'svsquare': 0x33DC,
  'syouwaerasquare': 0x337C,
  't': 0x0074,
  'tabengali': 0x09A4,
  'tackdown': 0x22A4,
  'tackleft': 0x22A3,
  'tadeva': 0x0924,
  'tagujarati': 0x0AA4,
  'tagurmukhi': 0x0A24,
  'taharabic': 0x0637,
  'tahfinalarabic': 0xFEC2,
  'tahinitialarabic': 0xFEC3,
  'tahiragana': 0x305F,
  'tahmedialarabic': 0xFEC4,
  'taisyouerasquare': 0x337D,
  'takatakana': 0x30BF,
  'takatakanahalfwidth': 0xFF80,
  'tatweelarabic': 0x0640,
  'tau': 0x03C4,
  'tav': 0x05EA,
  'tavdages': 0xFB4A,
  'tavdagesh': 0xFB4A,
  'tavdageshhebrew': 0xFB4A,
  'tavhebrew': 0x05EA,
  'tbar': 0x0167,
  'tbopomofo': 0x310A,
  'tcaron': 0x0165,
  'tccurl': 0x02A8,
  'tcedilla': 0x0163,
  'tcheharabic': 0x0686,
  'tchehfinalarabic': 0xFB7B,
  'tchehinitialarabic': 0xFB7C,
  'tchehmedialarabic': 0xFB7D,
  'tcircle': 0x24E3,
  'tcircumflexbelow': 0x1E71,
  'tcommaaccent': 0x0163,
  'tdieresis': 0x1E97,
  'tdotaccent': 0x1E6B,
  'tdotbelow': 0x1E6D,
  'tecyrillic': 0x0442,
  'tedescendercyrillic': 0x04AD,
  'teharabic': 0x062A,
  'tehfinalarabic': 0xFE96,
  'tehhahinitialarabic': 0xFCA2,
  'tehhahisolatedarabic': 0xFC0C,
  'tehinitialarabic': 0xFE97,
  'tehiragana': 0x3066,
  'tehjeeminitialarabic': 0xFCA1,
  'tehjeemisolatedarabic': 0xFC0B,
  'tehmarbutaarabic': 0x0629,
  'tehmarbutafinalarabic': 0xFE94,
  'tehmedialarabic': 0xFE98,
  'tehmeeminitialarabic': 0xFCA4,
  'tehmeemisolatedarabic': 0xFC0E,
  'tehnoonfinalarabic': 0xFC73,
  'tekatakana': 0x30C6,
  'tekatakanahalfwidth': 0xFF83,
  'telephone': 0x2121,
  'telephoneblack': 0x260E,
  'telishagedolahebrew': 0x05A0,
  'telishaqetanahebrew': 0x05A9,
  'tencircle': 0x2469,
  'tenideographicparen': 0x3229,
  'tenparen': 0x247D,
  'tenperiod': 0x2491,
  'tenroman': 0x2179,
  'tesh': 0x02A7,
  'tet': 0x05D8,
  'tetdagesh': 0xFB38,
  'tetdageshhebrew': 0xFB38,
  'tethebrew': 0x05D8,
  'tetsecyrillic': 0x04B5,
  'tevirhebrew': 0x059B,
  'tevirlefthebrew': 0x059B,
  'thabengali': 0x09A5,
  'thadeva': 0x0925,
  'thagujarati': 0x0AA5,
  'thagurmukhi': 0x0A25,
  'thalarabic': 0x0630,
  'thalfinalarabic': 0xFEAC,
  'thanthakhatlowleftthai': 0xF898,
  'thanthakhatlowrightthai': 0xF897,
  'thanthakhatthai': 0x0E4C,
  'thanthakhatupperleftthai': 0xF896,
  'theharabic': 0x062B,
  'thehfinalarabic': 0xFE9A,
  'thehinitialarabic': 0xFE9B,
  'thehmedialarabic': 0xFE9C,
  'thereexists': 0x2203,
  'therefore': 0x2234,
  'theta': 0x03B8,
  'theta1': 0x03D1,
  'thetasymbolgreek': 0x03D1,
  'thieuthacirclekorean': 0x3279,
  'thieuthaparenkorean': 0x3219,
  'thieuthcirclekorean': 0x326B,
  'thieuthkorean': 0x314C,
  'thieuthparenkorean': 0x320B,
  'thirteencircle': 0x246C,
  'thirteenparen': 0x2480,
  'thirteenperiod': 0x2494,
  'thonangmonthothai': 0x0E11,
  'thook': 0x01AD,
  'thophuthaothai': 0x0E12,
  'thorn': 0x00FE,
  'thothahanthai': 0x0E17,
  'thothanthai': 0x0E10,
  'thothongthai': 0x0E18,
  'thothungthai': 0x0E16,
  'thousandcyrillic': 0x0482,
  'thousandsseparatorarabic': 0x066C,
  'thousandsseparatorpersian': 0x066C,
  'three': 0x0033,
  'threearabic': 0x0663,
  'threebengali': 0x09E9,
  'threecircle': 0x2462,
  'threecircleinversesansserif': 0x278C,
  'threedeva': 0x0969,
  'threeeighths': 0x215C,
  'threegujarati': 0x0AE9,
  'threegurmukhi': 0x0A69,
  'threehackarabic': 0x0663,
  'threehangzhou': 0x3023,
  'threeideographicparen': 0x3222,
  'threeinferior': 0x2083,
  'threemonospace': 0xFF13,
  'threenumeratorbengali': 0x09F6,
  'threeoldstyle': 0xF733,
  'threeparen': 0x2476,
  'threeperiod': 0x248A,
  'threepersian': 0x06F3,
  'threequarters': 0x00BE,
  'threequartersemdash': 0xF6DE,
  'threeroman': 0x2172,
  'threesuperior': 0x00B3,
  'threethai': 0x0E53,
  'thzsquare': 0x3394,
  'tihiragana': 0x3061,
  'tikatakana': 0x30C1,
  'tikatakanahalfwidth': 0xFF81,
  'tikeutacirclekorean': 0x3270,
  'tikeutaparenkorean': 0x3210,
  'tikeutcirclekorean': 0x3262,
  'tikeutkorean': 0x3137,
  'tikeutparenkorean': 0x3202,
  'tilde': 0x02DC,
  'tildebelowcmb': 0x0330,
  'tildecmb': 0x0303,
  'tildecomb': 0x0303,
  'tildedoublecmb': 0x0360,
  'tildeoperator': 0x223C,
  'tildeoverlaycmb': 0x0334,
  'tildeverticalcmb': 0x033E,
  'timescircle': 0x2297,
  'tipehahebrew': 0x0596,
  'tipehalefthebrew': 0x0596,
  'tippigurmukhi': 0x0A70,
  'titlocyrilliccmb': 0x0483,
  'tiwnarmenian': 0x057F,
  'tlinebelow': 0x1E6F,
  'tmonospace': 0xFF54,
  'toarmenian': 0x0569,
  'tohiragana': 0x3068,
  'tokatakana': 0x30C8,
  'tokatakanahalfwidth': 0xFF84,
  'tonebarextrahighmod': 0x02E5,
  'tonebarextralowmod': 0x02E9,
  'tonebarhighmod': 0x02E6,
  'tonebarlowmod': 0x02E8,
  'tonebarmidmod': 0x02E7,
  'tonefive': 0x01BD,
  'tonesix': 0x0185,
  'tonetwo': 0x01A8,
  'tonos': 0x0384,
  'tonsquare': 0x3327,
  'topatakthai': 0x0E0F,
  'tortoiseshellbracketleft': 0x3014,
  'tortoiseshellbracketleftsmall': 0xFE5D,
  'tortoiseshellbracketleftvertical': 0xFE39,
  'tortoiseshellbracketright': 0x3015,
  'tortoiseshellbracketrightsmall': 0xFE5E,
  'tortoiseshellbracketrightvertical': 0xFE3A,
  'totaothai': 0x0E15,
  'tpalatalhook': 0x01AB,
  'tparen': 0x24AF,
  'trademark': 0x2122,
  'trademarksans': 0xF8EA,
  'trademarkserif': 0xF6DB,
  'tretroflexhook': 0x0288,
  'triagdn': 0x25BC,
  'triaglf': 0x25C4,
  'triagrt': 0x25BA,
  'triagup': 0x25B2,
  'ts': 0x02A6,
  'tsadi': 0x05E6,
  'tsadidagesh': 0xFB46,
  'tsadidageshhebrew': 0xFB46,
  'tsadihebrew': 0x05E6,
  'tsecyrillic': 0x0446,
  'tsere': 0x05B5,
  'tsere12': 0x05B5,
  'tsere1e': 0x05B5,
  'tsere2b': 0x05B5,
  'tserehebrew': 0x05B5,
  'tserenarrowhebrew': 0x05B5,
  'tserequarterhebrew': 0x05B5,
  'tserewidehebrew': 0x05B5,
  'tshecyrillic': 0x045B,
  'tsuperior': 0xF6F3,
  'ttabengali': 0x099F,
  'ttadeva': 0x091F,
  'ttagujarati': 0x0A9F,
  'ttagurmukhi': 0x0A1F,
  'tteharabic': 0x0679,
  'ttehfinalarabic': 0xFB67,
  'ttehinitialarabic': 0xFB68,
  'ttehmedialarabic': 0xFB69,
  'tthabengali': 0x09A0,
  'tthadeva': 0x0920,
  'tthagujarati': 0x0AA0,
  'tthagurmukhi': 0x0A20,
  'tturned': 0x0287,
  'tuhiragana': 0x3064,
  'tukatakana': 0x30C4,
  'tukatakanahalfwidth': 0xFF82,
  'tusmallhiragana': 0x3063,
  'tusmallkatakana': 0x30C3,
  'tusmallkatakanahalfwidth': 0xFF6F,
  'twelvecircle': 0x246B,
  'twelveparen': 0x247F,
  'twelveperiod': 0x2493,
  'twelveroman': 0x217B,
  'twentycircle': 0x2473,
  'twentyhangzhou': 0x5344,
  'twentyparen': 0x2487,
  'twentyperiod': 0x249B,
  'two': 0x0032,
  'twoarabic': 0x0662,
  'twobengali': 0x09E8,
  'twocircle': 0x2461,
  'twocircleinversesansserif': 0x278B,
  'twodeva': 0x0968,
  'twodotenleader': 0x2025,
  'twodotleader': 0x2025,
  'twodotleadervertical': 0xFE30,
  'twogujarati': 0x0AE8,
  'twogurmukhi': 0x0A68,
  'twohackarabic': 0x0662,
  'twohangzhou': 0x3022,
  'twoideographicparen': 0x3221,
  'twoinferior': 0x2082,
  'twomonospace': 0xFF12,
  'twonumeratorbengali': 0x09F5,
  'twooldstyle': 0xF732,
  'twoparen': 0x2475,
  'twoperiod': 0x2489,
  'twopersian': 0x06F2,
  'tworoman': 0x2171,
  'twostroke': 0x01BB,
  'twosuperior': 0x00B2,
  'twothai': 0x0E52,
  'twothirds': 0x2154,
  'u': 0x0075,
  'uacute': 0x00FA,
  'ubar': 0x0289,
  'ubengali': 0x0989,
  'ubopomofo': 0x3128,
  'ubreve': 0x016D,
  'ucaron': 0x01D4,
  'ucircle': 0x24E4,
  'ucircumflex': 0x00FB,
  'ucircumflexbelow': 0x1E77,
  'ucyrillic': 0x0443,
  'udattadeva': 0x0951,
  'udblacute': 0x0171,
  'udblgrave': 0x0215,
  'udeva': 0x0909,
  'udieresis': 0x00FC,
  'udieresisacute': 0x01D8,
  'udieresisbelow': 0x1E73,
  'udieresiscaron': 0x01DA,
  'udieresiscyrillic': 0x04F1,
  'udieresisgrave': 0x01DC,
  'udieresismacron': 0x01D6,
  'udotbelow': 0x1EE5,
  'ugrave': 0x00F9,
  'ugujarati': 0x0A89,
  'ugurmukhi': 0x0A09,
  'uhiragana': 0x3046,
  'uhookabove': 0x1EE7,
  'uhorn': 0x01B0,
  'uhornacute': 0x1EE9,
  'uhorndotbelow': 0x1EF1,
  'uhorngrave': 0x1EEB,
  'uhornhookabove': 0x1EED,
  'uhorntilde': 0x1EEF,
  'uhungarumlaut': 0x0171,
  'uhungarumlautcyrillic': 0x04F3,
  'uinvertedbreve': 0x0217,
  'ukatakana': 0x30A6,
  'ukatakanahalfwidth': 0xFF73,
  'ukcyrillic': 0x0479,
  'ukorean': 0x315C,
  'umacron': 0x016B,
  'umacroncyrillic': 0x04EF,
  'umacrondieresis': 0x1E7B,
  'umatragurmukhi': 0x0A41,
  'umonospace': 0xFF55,
  'underscore': 0x005F,
  'underscoredbl': 0x2017,
  'underscoremonospace': 0xFF3F,
  'underscorevertical': 0xFE33,
  'underscorewavy': 0xFE4F,
  'union': 0x222A,
  'universal': 0x2200,
  'uogonek': 0x0173,
  'uparen': 0x24B0,
  'upblock': 0x2580,
  'upperdothebrew': 0x05C4,
  'upsilon': 0x03C5,
  'upsilondieresis': 0x03CB,
  'upsilondieresistonos': 0x03B0,
  'upsilonlatin': 0x028A,
  'upsilontonos': 0x03CD,
  'uptackbelowcmb': 0x031D,
  'uptackmod': 0x02D4,
  'uragurmukhi': 0x0A73,
  'uring': 0x016F,
  'ushortcyrillic': 0x045E,
  'usmallhiragana': 0x3045,
  'usmallkatakana': 0x30A5,
  'usmallkatakanahalfwidth': 0xFF69,
  'ustraightcyrillic': 0x04AF,
  'ustraightstrokecyrillic': 0x04B1,
  'utilde': 0x0169,
  'utildeacute': 0x1E79,
  'utildebelow': 0x1E75,
  'uubengali': 0x098A,
  'uudeva': 0x090A,
  'uugujarati': 0x0A8A,
  'uugurmukhi': 0x0A0A,
  'uumatragurmukhi': 0x0A42,
  'uuvowelsignbengali': 0x09C2,
  'uuvowelsigndeva': 0x0942,
  'uuvowelsigngujarati': 0x0AC2,
  'uvowelsignbengali': 0x09C1,
  'uvowelsigndeva': 0x0941,
  'uvowelsigngujarati': 0x0AC1,
  'v': 0x0076,
  'vadeva': 0x0935,
  'vagujarati': 0x0AB5,
  'vagurmukhi': 0x0A35,
  'vakatakana': 0x30F7,
  'vav': 0x05D5,
  'vavdagesh': 0xFB35,
  'vavdagesh65': 0xFB35,
  'vavdageshhebrew': 0xFB35,
  'vavhebrew': 0x05D5,
  'vavholam': 0xFB4B,
  'vavholamhebrew': 0xFB4B,
  'vavvavhebrew': 0x05F0,
  'vavyodhebrew': 0x05F1,
  'vcircle': 0x24E5,
  'vdotbelow': 0x1E7F,
  'vecyrillic': 0x0432,
  'veharabic': 0x06A4,
  'vehfinalarabic': 0xFB6B,
  'vehinitialarabic': 0xFB6C,
  'vehmedialarabic': 0xFB6D,
  'vekatakana': 0x30F9,
  'venus': 0x2640,
  'verticalbar': 0x007C,
  'verticallineabovecmb': 0x030D,
  'verticallinebelowcmb': 0x0329,
  'verticallinelowmod': 0x02CC,
  'verticallinemod': 0x02C8,
  'vewarmenian': 0x057E,
  'vhook': 0x028B,
  'vikatakana': 0x30F8,
  'viramabengali': 0x09CD,
  'viramadeva': 0x094D,
  'viramagujarati': 0x0ACD,
  'visargabengali': 0x0983,
  'visargadeva': 0x0903,
  'visargagujarati': 0x0A83,
  'vmonospace': 0xFF56,
  'voarmenian': 0x0578,
  'voicediterationhiragana': 0x309E,
  'voicediterationkatakana': 0x30FE,
  'voicedmarkkana': 0x309B,
  'voicedmarkkanahalfwidth': 0xFF9E,
  'vokatakana': 0x30FA,
  'vparen': 0x24B1,
  'vtilde': 0x1E7D,
  'vturned': 0x028C,
  'vuhiragana': 0x3094,
  'vukatakana': 0x30F4,
  'w': 0x0077,
  'wacute': 0x1E83,
  'waekorean': 0x3159,
  'wahiragana': 0x308F,
  'wakatakana': 0x30EF,
  'wakatakanahalfwidth': 0xFF9C,
  'wakorean': 0x3158,
  'wasmallhiragana': 0x308E,
  'wasmallkatakana': 0x30EE,
  'wattosquare': 0x3357,
  'wavedash': 0x301C,
  'wavyunderscorevertical': 0xFE34,
  'wawarabic': 0x0648,
  'wawfinalarabic': 0xFEEE,
  'wawhamzaabovearabic': 0x0624,
  'wawhamzaabovefinalarabic': 0xFE86,
  'wbsquare': 0x33DD,
  'wcircle': 0x24E6,
  'wcircumflex': 0x0175,
  'wdieresis': 0x1E85,
  'wdotaccent': 0x1E87,
  'wdotbelow': 0x1E89,
  'wehiragana': 0x3091,
  'weierstrass': 0x2118,
  'wekatakana': 0x30F1,
  'wekorean': 0x315E,
  'weokorean': 0x315D,
  'wgrave': 0x1E81,
  'whitebullet': 0x25E6,
  'whitecircle': 0x25CB,
  'whitecircleinverse': 0x25D9,
  'whitecornerbracketleft': 0x300E,
  'whitecornerbracketleftvertical': 0xFE43,
  'whitecornerbracketright': 0x300F,
  'whitecornerbracketrightvertical': 0xFE44,
  'whitediamond': 0x25C7,
  'whitediamondcontainingblacksmalldiamond': 0x25C8,
  'whitedownpointingsmalltriangle': 0x25BF,
  'whitedownpointingtriangle': 0x25BD,
  'whiteleftpointingsmalltriangle': 0x25C3,
  'whiteleftpointingtriangle': 0x25C1,
  'whitelenticularbracketleft': 0x3016,
  'whitelenticularbracketright': 0x3017,
  'whiterightpointingsmalltriangle': 0x25B9,
  'whiterightpointingtriangle': 0x25B7,
  'whitesmallsquare': 0x25AB,
  'whitesmilingface': 0x263A,
  'whitesquare': 0x25A1,
  'whitestar': 0x2606,
  'whitetelephone': 0x260F,
  'whitetortoiseshellbracketleft': 0x3018,
  'whitetortoiseshellbracketright': 0x3019,
  'whiteuppointingsmalltriangle': 0x25B5,
  'whiteuppointingtriangle': 0x25B3,
  'wihiragana': 0x3090,
  'wikatakana': 0x30F0,
  'wikorean': 0x315F,
  'wmonospace': 0xFF57,
  'wohiragana': 0x3092,
  'wokatakana': 0x30F2,
  'wokatakanahalfwidth': 0xFF66,
  'won': 0x20A9,
  'wonmonospace': 0xFFE6,
  'wowaenthai': 0x0E27,
  'wparen': 0x24B2,
  'wring': 0x1E98,
  'wsuperior': 0x02B7,
  'wturned': 0x028D,
  'wynn': 0x01BF,
  'x': 0x0078,
  'xabovecmb': 0x033D,
  'xbopomofo': 0x3112,
  'xcircle': 0x24E7,
  'xdieresis': 0x1E8D,
  'xdotaccent': 0x1E8B,
  'xeharmenian': 0x056D,
  'xi': 0x03BE,
  'xmonospace': 0xFF58,
  'xparen': 0x24B3,
  'xsuperior': 0x02E3,
  'y': 0x0079,
  'yaadosquare': 0x334E,
  'yabengali': 0x09AF,
  'yacute': 0x00FD,
  'yadeva': 0x092F,
  'yaekorean': 0x3152,
  'yagujarati': 0x0AAF,
  'yagurmukhi': 0x0A2F,
  'yahiragana': 0x3084,
  'yakatakana': 0x30E4,
  'yakatakanahalfwidth': 0xFF94,
  'yakorean': 0x3151,
  'yamakkanthai': 0x0E4E,
  'yasmallhiragana': 0x3083,
  'yasmallkatakana': 0x30E3,
  'yasmallkatakanahalfwidth': 0xFF6C,
  'yatcyrillic': 0x0463,
  'ycircle': 0x24E8,
  'ycircumflex': 0x0177,
  'ydieresis': 0x00FF,
  'ydotaccent': 0x1E8F,
  'ydotbelow': 0x1EF5,
  'yeharabic': 0x064A,
  'yehbarreearabic': 0x06D2,
  'yehbarreefinalarabic': 0xFBAF,
  'yehfinalarabic': 0xFEF2,
  'yehhamzaabovearabic': 0x0626,
  'yehhamzaabovefinalarabic': 0xFE8A,
  'yehhamzaaboveinitialarabic': 0xFE8B,
  'yehhamzaabovemedialarabic': 0xFE8C,
  'yehinitialarabic': 0xFEF3,
  'yehmedialarabic': 0xFEF4,
  'yehmeeminitialarabic': 0xFCDD,
  'yehmeemisolatedarabic': 0xFC58,
  'yehnoonfinalarabic': 0xFC94,
  'yehthreedotsbelowarabic': 0x06D1,
  'yekorean': 0x3156,
  'yen': 0x00A5,
  'yenmonospace': 0xFFE5,
  'yeokorean': 0x3155,
  'yeorinhieuhkorean': 0x3186,
  'yerahbenyomohebrew': 0x05AA,
  'yerahbenyomolefthebrew': 0x05AA,
  'yericyrillic': 0x044B,
  'yerudieresiscyrillic': 0x04F9,
  'yesieungkorean': 0x3181,
  'yesieungpansioskorean': 0x3183,
  'yesieungsioskorean': 0x3182,
  'yetivhebrew': 0x059A,
  'ygrave': 0x1EF3,
  'yhook': 0x01B4,
  'yhookabove': 0x1EF7,
  'yiarmenian': 0x0575,
  'yicyrillic': 0x0457,
  'yikorean': 0x3162,
  'yinyang': 0x262F,
  'yiwnarmenian': 0x0582,
  'ymonospace': 0xFF59,
  'yod': 0x05D9,
  'yoddagesh': 0xFB39,
  'yoddageshhebrew': 0xFB39,
  'yodhebrew': 0x05D9,
  'yodyodhebrew': 0x05F2,
  'yodyodpatahhebrew': 0xFB1F,
  'yohiragana': 0x3088,
  'yoikorean': 0x3189,
  'yokatakana': 0x30E8,
  'yokatakanahalfwidth': 0xFF96,
  'yokorean': 0x315B,
  'yosmallhiragana': 0x3087,
  'yosmallkatakana': 0x30E7,
  'yosmallkatakanahalfwidth': 0xFF6E,
  'yotgreek': 0x03F3,
  'yoyaekorean': 0x3188,
  'yoyakorean': 0x3187,
  'yoyakthai': 0x0E22,
  'yoyingthai': 0x0E0D,
  'yparen': 0x24B4,
  'ypogegrammeni': 0x037A,
  'ypogegrammenigreekcmb': 0x0345,
  'yr': 0x01A6,
  'yring': 0x1E99,
  'ysuperior': 0x02B8,
  'ytilde': 0x1EF9,
  'yturned': 0x028E,
  'yuhiragana': 0x3086,
  'yuikorean': 0x318C,
  'yukatakana': 0x30E6,
  'yukatakanahalfwidth': 0xFF95,
  'yukorean': 0x3160,
  'yusbigcyrillic': 0x046B,
  'yusbigiotifiedcyrillic': 0x046D,
  'yuslittlecyrillic': 0x0467,
  'yuslittleiotifiedcyrillic': 0x0469,
  'yusmallhiragana': 0x3085,
  'yusmallkatakana': 0x30E5,
  'yusmallkatakanahalfwidth': 0xFF6D,
  'yuyekorean': 0x318B,
  'yuyeokorean': 0x318A,
  'yyabengali': 0x09DF,
  'yyadeva': 0x095F,
  'z': 0x007A,
  'zaarmenian': 0x0566,
  'zacute': 0x017A,
  'zadeva': 0x095B,
  'zagurmukhi': 0x0A5B,
  'zaharabic': 0x0638,
  'zahfinalarabic': 0xFEC6,
  'zahinitialarabic': 0xFEC7,
  'zahiragana': 0x3056,
  'zahmedialarabic': 0xFEC8,
  'zainarabic': 0x0632,
  'zainfinalarabic': 0xFEB0,
  'zakatakana': 0x30B6,
  'zaqefgadolhebrew': 0x0595,
  'zaqefqatanhebrew': 0x0594,
  'zarqahebrew': 0x0598,
  'zayin': 0x05D6,
  'zayindagesh': 0xFB36,
  'zayindageshhebrew': 0xFB36,
  'zayinhebrew': 0x05D6,
  'zbopomofo': 0x3117,
  'zcaron': 0x017E,
  'zcircle': 0x24E9,
  'zcircumflex': 0x1E91,
  'zcurl': 0x0291,
  'zdot': 0x017C,
  'zdotaccent': 0x017C,
  'zdotbelow': 0x1E93,
  'zecyrillic': 0x0437,
  'zedescendercyrillic': 0x0499,
  'zedieresiscyrillic': 0x04DF,
  'zehiragana': 0x305C,
  'zekatakana': 0x30BC,
  'zero': 0x0030,
  'zeroarabic': 0x0660,
  'zerobengali': 0x09E6,
  'zerodeva': 0x0966,
  'zerogujarati': 0x0AE6,
  'zerogurmukhi': 0x0A66,
  'zerohackarabic': 0x0660,
  'zeroinferior': 0x2080,
  'zeromonospace': 0xFF10,
  'zerooldstyle': 0xF730,
  'zeropersian': 0x06F0,
  'zerosuperior': 0x2070,
  'zerothai': 0x0E50,
  'zerowidthjoiner': 0xFEFF,
  'zerowidthnonjoiner': 0x200C,
  'zerowidthspace': 0x200B,
  'zeta': 0x03B6,
  'zhbopomofo': 0x3113,
  'zhearmenian': 0x056A,
  'zhebrevecyrillic': 0x04C2,
  'zhecyrillic': 0x0436,
  'zhedescendercyrillic': 0x0497,
  'zhedieresiscyrillic': 0x04DD,
  'zihiragana': 0x3058,
  'zikatakana': 0x30B8,
  'zinorhebrew': 0x05AE,
  'zlinebelow': 0x1E95,
  'zmonospace': 0xFF5A,
  'zohiragana': 0x305E,
  'zokatakana': 0x30BE,
  'zparen': 0x24B5,
  'zretroflexhook': 0x0290,
  'zstroke': 0x01B6,
  'zuhiragana': 0x305A,
  'zukatakana': 0x30BA,
  '.notdef': 0x0000
}

class PDFImage {
  private image: JpegStream | null = null;
  private width: number = 0.0;
  private height: number = 0.0;
  private interpolate: boolean = false;
  private imageMask: boolean = false;
  private bpc: number = 0;
  private bps: number = 0;
  private colorSpace: DeviceRgbCS | null = null;
  private numComps: number = 0;
  private decode: number[] | null = null;
  private needsDecode: boolean = false;
  private decodeCoefficients: number[] | null = null;
  private decodeAddends: number[] | null = null;
  private smask: PDFImage | null = null;

  constructor(xref?: XRef, res?: Dict, image?: JpegStream, inline?: boolean, smask?: JpegStream) {
    this.image = image;

    if (image.getParams) {
      TODO('get params from actual stream');
    }

    let dict: Dict = image.dict;
    this.width = dict.get('Width', 'W') as number;
    this.height = dict.get('Height', 'H') as number;

    if (this.width < 1 || this.height < 1) {
      error(`Invalid image width: ${this.width} or height: ${this.height}`);
    }

    this.interpolate = dict.get('Interpolate', 'I') as boolean || false;
    this.imageMask = dict.get('ImageMask', 'IM') as boolean || false;

    let bitsPerComponent: number = image.bitsPerComponent;
    if (bitsPerComponent !== null && bitsPerComponent !== undefined){
      bitsPerComponent = dict.get('BitsPerComponent', 'BPC') as number;
      if (bitsPerComponent !== null && bitsPerComponent !== undefined){
        if (this.imageMask) {
          bitsPerComponent = 1;
        }else {
          error(`Bits per component missing in image: ${this.imageMask}`);
        }
      }
    }
    this.bpc = bitsPerComponent;

    if (!this.imageMask) {
      let colorSpace: Name = dict.get('ColorSpace', 'CS') as Name;
      if (!colorSpace) {
        TODO('JPX images (which don"t require color spaces');
        colorSpace = new Name('DeviceRGB');
      }
      this.colorSpace = ColorSpace.parse(colorSpace, xref, res) as DeviceRgbCS;
      this.numComps = this.colorSpace.numComps;
    }

    this.decode = dict.get('Decode', 'D') as number[];
    this.needsDecode = false;
    if ((this.decode !== null && this.decode !== undefined) && this.colorSpace !== null && this.colorSpace !== undefined && !this.colorSpace.isDefaultDecode(this.decode)) {
      this.needsDecode = true;

      let max = (1 << bitsPerComponent) - 1;
      this.decodeCoefficients = new Array<number>() ;
      this.decodeAddends = new Array<number>();
      for (let i = 0; i < this.decode.length; i += Number_2) {
        let dmin: number = this.decode[i];
        let dmax: number = this.decode[i + 1];
        this.decodeCoefficients.push(dmax - dmin);
        this.decodeAddends.push(max * dmin);
      }
    }

    let mask: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = dict.get('Mask');
    if (mask !== undefined && mask !== null) {
      TODO('masked images');
    } else if (smask !== null && smask !== undefined) {
      this.smask = new PDFImage(xref, res, smask, false);
    }
  }

  static handleImageData(handler: MessageHandler, xref: XRef, res: Dict, image: JpegStream, promise: PDFPromise) {
    if (image instanceof JpegStream && image.isNativelyDecodable(xref, res)) {

      let dict: Dict = image.dict;
      let colorSpace: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = dict.get('ColorSpace', 'CS');
      colorSpace = ColorSpace.parse(colorSpace, xref, res) as DeviceRgbCS;
      let numComps: number = (colorSpace as DeviceRgbCS).numComps;
      handler.send("JpegDecode", [image.getIR(), numComps], (message: EventMessageData) => {
        let data: Uint8Array = (message as EventMessageData).data as Uint8Array;
        let stream: Stream = new Stream(data, 0, data.length, image.dict);
        promise.resolve(stream);
      });
    } else {
      promise.resolve(image);
    }
  }

  decodeAndClamp(value: number, addend: number, coefficient: number, max: number): number {
    let decodedValue: number = addend + value * coefficient;
    return decodedValue < 0 ? 0 : decodedValue > max ? max : decodedValue;
  }

  static buildImage(callback: (image: PDFImage) => void, handler: MessageHandler, xref: XRef, res: Dict, image: JpegStream, inline: boolean) {
    let imageDataPromise: PDFPromise = new PDFPromise();
    let smaskPromise: PDFPromise = new PDFPromise();

    PDFPromise.all([imageDataPromise, smaskPromise]).then((results) => {
      let imageData: JpegStream = results[0];
      let smaskData: JpegStream = results[1];
      let pdfImage:PDFImage = new PDFImage(xref, res, imageData, inline, smaskData);
      callback(pdfImage);
    });

    PDFImage.handleImageData(handler, xref, res, image, imageDataPromise);

    let smask: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = image.dict.get('SMask');
    if (smask !== undefined && smask !==  null) {
      PDFImage.handleImageData(handler, xref, res, smask as JpegStream , smaskPromise);
    } else {
      smaskPromise.resolve();
    }
  }


  static resize(pixels: Uint8Array, bpc: number, components: number, w1: number, h1: number, w2: number, h2: number): Uint8Array|Uint16Array|Uint32Array {
    let length: number = w2 * h2 * components;

    let temp: Uint8Array | Uint16Array | Uint32Array;
    if (bpc <= Number_8) {
      temp = new Uint8Array(length).fill(0);
    }else if (bpc <= Number_16) {
      temp = new Uint16Array(length).fill(0);
    }else{
      temp = new Uint32Array(length).fill(0);
    }

    let xRatio: number = w1 / w2;
    let yRatio: number = h1 / h2;
    let px:number;
    let py:number;
    let newIndex:number;
    let oldIndex:number;

    for (let i = 0; i < h2; i++) {
      for (let j = 0; j < w2; j++) {
        px = Math.floor(j * xRatio);
        py = Math.floor(i * yRatio);
        newIndex = i * w2 + j;
        oldIndex = py * w1 + px;

        if (components === 1) {
          temp[newIndex] = pixels[oldIndex];
        } else if (components === Number_3) {
          newIndex *= Number_3;
          oldIndex *= Number_3;
          temp[newIndex] = pixels[oldIndex];
          temp[newIndex + 1] = pixels[oldIndex + 1];
          temp[newIndex + Number_2] = pixels[oldIndex + Number_2];

        }
      }
    }

    return temp;
  }

  get drawWidth(): number {
    if (!this.smask) {
      return this.width;
    }
    return Math.max(this.width, this.smask.width);
  }

  get drawHeight(): number {
    if (!this.smask) {
      return this.height;
    }
    return Math.max(this.height, this.smask.height);
  }

  getComponents(buffer: Uint8Array): Uint8Array | Uint16Array | Uint32Array {
    let bpc: number = this.bpc;
    let needsDecode: boolean = this.needsDecode;
    let decodeMap: number[] = this.decode;


    if (bpc == Number_8 && !needsDecode) {
      return buffer;
    }

    let bufferLength: number = buffer.length;
    let width: number = this.width;
    let height: number = this.height;
    let numComps: number = this.numComps;

    let length: number = width * height * numComps;
    let bufferPos: number = 0;
    let output: Uint8Array | Uint16Array | Uint32Array;
    if (bpc <= Number_8) {
      output = new Uint8Array(length).fill(0);
    }else if (bpc <= Number_16) {
      output = new Uint16Array(length).fill(0);
    }else{
      output = new Uint32Array(length).fill(0);
    }


    let rowComps: number = width * numComps;
    let decodeAddends: number[];
    let decodeCoefficients: number[];
    if (needsDecode) {
      decodeAddends = this.decodeAddends;
      decodeCoefficients = this.decodeCoefficients;
    }
    let max: number = (1 << bpc) - 1;

    if (bpc == Number_8) {

      for (let i = 0; i < length; ++i) {
        let compIndex: number = i % numComps;
        let value: number = buffer[i];
        value = this.decodeAndClamp(value, decodeAddends[compIndex],
          decodeCoefficients[compIndex], max);
        output[i] = value;
      }
    } else if (bpc == 1) {
      let valueZero: number = 0;
      let valueOne: number = 1;
      if (decodeMap !== null && decodeMap !== undefined){
        valueZero = decodeMap[0] ? 1 : 0;
        valueOne = decodeMap[1] ? 1 : 0;
      }
      let mask: number = 0;
      let buf: number = 0;

      for (let i = 0; i < length; ++i) {
        if (i % rowComps == 0) {
          mask = 0;
          buf = 0;
        } else {
          mask >>= 1;
        }

        if (mask <= 0) {
          buf = buffer[bufferPos];
          bufferPos += 1;
          mask =Number_128;
        }

        output[i] = !(buf & mask) ? valueZero : valueOne;
      }
    } else {
      let bits: number = 0
      let buf: number = 0;
      for (let i = 0; i < length; ++i) {
        if (i % rowComps == 0) {
          buf = 0;
          bits = 0;
        }

        while (bits < bpc) {
          buf = (buf << Number_8) | buffer[bufferPos];
          bufferPos += 1
          bits += Number_8;
        }

        let remainingBits: number = bits - bpc;
        let value: number = buf >> remainingBits;
        if (needsDecode) {
          let compIndex: number = i % numComps;
          value = this.decodeAndClamp(value, decodeAddends[compIndex],
            decodeCoefficients[compIndex], max);
        }
        output[i] = value;
        buf = buf & ((1 << remainingBits) - 1);
        bits = remainingBits;
      }
    }
    return output;
  }

  getOpacity(width: number, height: number): Uint8Array {

    let buf: Uint8Array;
    let smask: PDFImage = this.smask;
    if (smask !== null && smask !== undefined){
      let sw: number = smask.width;
      let sh: number = smask.height;
      buf = new Uint8Array(sw * sh);
      smask.fillGrayBuffer(buf);
      if (sw !== width || sh !== height) {
        buf = PDFImage.resize(buf, smask.bps, 1, sw, sh, width, height) as Uint8Array;
      }
    } else {
      buf = new Uint8Array(width * height).fill(0);
      for (let i = 0; i < width * height; ++i) {
        buf[i] = Number_255;
      }
    }
    return buf;
  }

  applyStencilMask(buffer: Uint8Array, inverseDecode: boolean): void {
    buffer = buffer;
    let width: number = this.width;
    let height: number = this.height;
    let bitStrideLength: number = (width + Number_7) >> Number_3;
    let imgArray: Uint8Array = this.getImageBytes(bitStrideLength * height);
    let imgArrayPos: number = 0;
    let mask: number;
    let buf: number;
    let bufferPos: number = Number_3;

    for (let i = 0; i < height; i++) {
      mask = 0;
      for (let j = 0; j < width; j++) {
        if (mask == 0) {
          buf = imgArray[imgArrayPos];
          imgArrayPos += 1;
          mask =Number_128;
        }
        if (((buf & mask) === 0) == inverseDecode) {
          buffer[bufferPos] = 0;
        }
        bufferPos += Number_4;
        mask >>= 1;
      }
    }
  }

  fillRgbaBuffer(buffer: Uint8Array, width: number, height: number): void {
    buffer = buffer;
    let numComps: number = this.numComps;
    let originalWidth: number = this.width;
    let originalHeight: number = this.height;
    let bpc: number = this.bpc;

    let rowBytes: number = (originalWidth * numComps * bpc + Number_7) >> Number_3;
    let imgArray: Uint8Array = this.getImageBytes(originalHeight * rowBytes);

    let comps: Uint8Array = this.colorSpace.getRgbBuffer(Array.from(this.getComponents(imgArray)), bpc);
    if (originalWidth !== width || originalHeight !== height) {
      comps = PDFImage.resize(comps, this.bpc, Number_3, originalWidth, originalHeight, width, height) as Uint8Array;
    }
    let compsPos: number = 0;
    let opacity: Uint8Array = this.getOpacity(width, height);
    let opacityPos: number = 0;
    let length: number = width * height * Number_4;

    for (let i = 0; i < length; i += Number_4) {
      buffer[i] = comps[compsPos];
      compsPos += 1;
      buffer[i + 1] = comps[compsPos];
      compsPos += 1;
      buffer[i + Number_2] = comps[compsPos];
      compsPos += 1;
      buffer[i + Number_3] = opacity[opacityPos];
      opacityPos += 1;
    }
  }

  fillGrayBuffer(buffer: Uint8Array): void {
    buffer = buffer;
    let numComps: number = this.numComps;
    if (numComps !== 1) {
      error(`Reading grayscale from a color image: ${numComps}`);
    }

    let width: number = this.width;
    let height: number = this.height;
    let bpc: number = this.bpc;

    let rowBytes: number = (width * numComps * bpc + Number_7) >> Number_3;
    let imgArray: Uint8Array = this.getImageBytes(height * rowBytes);

    let comps: Uint8Array | Uint16Array | Uint32Array = this.getComponents(imgArray);
    let length: number = width * height;
    let scale: number = Number_255 / ((1 << bpc) - 1);
    for (let i = 0; i < length; ++i) {
      buffer[i] = (scale * comps[i]);
    }
  }

  getImageBytes(length: number): Uint8Array{
    this.image.reset();
    return this.image.getBytes(length);
  }

}

let loadJpegStream = (id?: string, imageData?: string, objs?: PDFObjects) => {
  let img: Image = new Image();
  img.onload = () => {
    objs.resolve(id, img);
  };
  img.src = 'data:image/jpeg;base64,' + PdfJS_window.btoa(imageData);
}

let Metrics = new Map<string, number | Map<string, number>>([
  ['Courier', 600], ['Courier-Bold', 600], ['Courier-BoldOblique', 600], ['Courier-Oblique', 600], ['Helvetica', new Map<string, number>([
    ['space', 278], ['exclam', 278], ['quotedbl', 355], ['numbersign', 556], ['dollar', 556], ['percent', 889], ['ampersand', 667], ['quoteright', 222], ['parenleft', 333], ['parenright', 333], ['asterisk', 389], ['plus', 584], ['comma', 278], ['hyphen', 333], ['period', 278], ['slash', 278], ['zero', 556], ['one', 556], ['two', 556], ['three', 556], ['four', 556], ['five', 556], ['six', 556], ['seven', 556], ['eight', 556], ['nine', 556], ['colon', 278], ['semicolon', 278], ['less', 584], ['equal', 584], ['greater', 584], ['question', 556], ['at', 1015], ['A', 667], ['B', 667], ['C', 722], ['D', 722], ['E', 667], ['F', 611], ['G', 778], ['H', 722], ['I', 278], ['J', 500], ['K', 667], ['L', 556], ['M', 833], ['N', 722], ['O', 778], ['P', 667], ['Q', 778], ['R', 722], ['S', 667], ['T', 611], ['U', 722], ['V', 667], ['W', 944], ['X', 667], ['Y', 667], ['Z', 611], ['bracketleft', 278], ['backslash', 278], ['bracketright', 278], ['asciicircum', 469], ['underscore', 556], ['quoteleft', 222], ['a', 556], ['b', 556], ['c', 500], ['d', 556], ['e', 556], ['f', 278], ['g', 556], ['h', 556], ['i', 222], ['j', 222], ['k', 500], ['l', 222], ['m', 833], ['n', 556], ['o', 556], ['p', 556], ['q', 556], ['r', 333], ['s', 500], ['t', 278], ['u', 556], ['v', 500], ['w', 722], ['x', 500], ['y', 500], ['z', 500], ['braceleft', 334], ['bar', 260], ['braceright', 334], ['asciitilde', 584], ['exclamdown', 333], ['cent', 556], ['sterling', 556], ['fraction', 167], ['yen', 556], ['florin', 556], ['section', 556], ['currency', 556], ['quotesingle', 191], ['quotedblleft', 333], ['guillemotleft', 556], ['guilsinglleft', 333], ['guilsinglright', 333], ['fi', 500], ['fl', 500], ['endash', 556], ['dagger', 556], ['daggerdbl', 556], ['periodcentered', 278], ['paragraph', 537], ['bullet', 350], ['quotesinglbase', 222], ['quotedblbase', 333], ['quotedblright', 333], ['guillemotright', 556], ['ellipsis', 1000], ['perthousand', 1000], ['questiondown', 611], ['grave', 333], ['acute', 333], ['circumflex', 333], ['tilde', 333], ['macron', 333], ['breve', 333], ['dotaccent', 333], ['dieresis', 333], ['ring', 333], ['cedilla', 333], ['hungarumlaut', 333], ['ogonek', 333], ['caron', 333], ['emdash', 1000], ['AE', 1000], ['ordfeminine', 370], ['Lslash', 556], ['Oslash', 778], ['OE', 1000], ['ordmasculine', 365], ['ae', 889], ['dotlessi', 278], ['lslash', 222], ['oslash', 611], ['oe', 944], ['germandbls', 611], ['Idieresis', 278], ['eacute', 556], ['abreve', 556], ['uhungarumlaut', 556], ['ecaron', 556], ['Ydieresis', 667], ['divide', 584], ['Yacute', 667], ['Acircumflex', 667], ['aacute', 556], ['Ucircumflex', 722], ['yacute', 500], ['scommaaccent', 500], ['ecircumflex', 556], ['Uring', 722], ['Udieresis', 722], ['aogonek', 556], ['Uacute', 722], ['uogonek', 556], ['Edieresis', 667], ['Dcroat', 722], ['commaaccent', 250], ['copyright', 737], ['Emacron', 667], ['ccaron', 500], ['aring', 556], ['Ncommaaccent', 722], ['lacute', 222], ['agrave', 556], ['Tcommaaccent', 611], ['Cacute', 722], ['atilde', 556], ['Edotaccent', 667], ['scaron', 500], ['scedilla', 500], ['iacute', 278], ['lozenge', 471], ['Rcaron', 722], ['Gcommaaccent', 778], ['ucircumflex', 556], ['acircumflex', 556], ['Amacron', 667], ['rcaron', 333], ['ccedilla', 500], ['Zdotaccent', 611], ['Thorn', 667], ['Omacron', 778], ['Racute', 722], ['Sacute', 667], ['dcaron', 643], ['Umacron', 722], ['uring', 556], ['threesuperior', 333], ['Ograve', 778], ['Agrave', 667], ['Abreve', 667], ['multiply', 584], ['uacute', 556], ['Tcaron', 611], ['partialdiff', 476], ['ydieresis', 500], ['Nacute', 722], ['icircumflex', 278], ['Ecircumflex', 667], ['adieresis', 556], ['edieresis', 556], ['cacute', 500], ['nacute', 556], ['umacron', 556], ['Ncaron', 722], ['Iacute', 278], ['plusminus', 584], ['brokenbar', 260], ['registered', 737], ['Gbreve', 778], ['Idotaccent', 278], ['summation', 600], ['Egrave', 667], ['racute', 333], ['omacron', 556], ['Zacute', 611], ['Zcaron', 611], ['greaterequal', 549], ['Eth', 722], ['Ccedilla', 722], ['lcommaaccent', 222], ['tcaron', 317], ['eogonek', 556], ['Uogonek', 722], ['Aacute', 667], ['Adieresis', 667], ['egrave', 556], ['zacute', 500], ['iogonek', 222], ['Oacute', 778], ['oacute', 556], ['amacron', 556], ['sacute', 500], ['idieresis', 278], ['Ocircumflex', 778], ['Ugrave', 722], ['Delta', 612], ['thorn', 556], ['twosuperior', 333], ['Odieresis', 778], ['mu', 556], ['igrave', 278], ['ohungarumlaut', 556], ['Eogonek', 667], ['dcroat', 556], ['threequarters', 834], ['Scedilla', 667], ['lcaron', 299], ['Kcommaaccent', 667], ['Lacute', 556], ['trademark', 1000], ['edotaccent', 556], ['Igrave', 278], ['Imacron', 278], ['Lcaron', 556], ['onehalf', 834], ['lessequal', 549], ['ocircumflex', 556], ['ntilde', 556], ['Uhungarumlaut', 722], ['Eacute', 667], ['emacron', 556], ['gbreve', 556], ['onequarter', 834], ['Scaron', 667], ['Scommaaccent', 667], ['Ohungarumlaut', 778], ['degree', 400], ['ograve', 556], ['Ccaron', 722], ['ugrave', 556], ['radical', 453], ['Dcaron', 722], ['rcommaaccent', 333], ['Ntilde', 722], ['otilde', 556], ['Rcommaaccent', 722], ['Lcommaaccent', 556], ['Atilde', 667], ['Aogonek', 667], ['Aring', 667], ['Otilde', 778], ['zdotaccent', 500], ['Ecaron', 667], ['Iogonek', 278], ['kcommaaccent', 500], ['minus', 584], ['Icircumflex', 278], ['ncaron', 556], ['tcommaaccent', 278], ['logicalnot', 584], ['odieresis', 556], ['udieresis', 556], ['notequal', 549], ['gcommaaccent', 556], ['eth', 556], ['zcaron', 500], ['ncommaaccent', 556], ['onesuperior', 333], ['imacron', 278], ['Euro', 556],
  ])], ['Helvetica-Bold', new Map<string, number>([
    ['space', 278], ['exclam', 333], ['quotedbl', 474], ['numbersign', 556], ['dollar', 556], ['percent', 889], ['ampersand', 722], ['quoteright', 278], ['parenleft', 333], ['parenright', 333], ['asterisk', 389], ['plus', 584], ['comma', 278], ['hyphen', 333], ['period', 278], ['slash', 278], ['zero', 556], ['one', 556], ['two', 556], ['three', 556], ['four', 556], ['five', 556], ['six', 556], ['seven', 556], ['eight', 556], ['nine', 556], ['colon', 333], ['semicolon', 333], ['less', 584], ['equal', 584], ['greater', 584], ['question', 611], ['at', 975], ['A', 722], ['B', 722], ['C', 722], ['D', 722], ['E', 667], ['F', 611], ['G', 778], ['H', 722], ['I', 278], ['J', 556], ['K', 722], ['L', 611], ['M', 833], ['N', 722], ['O', 778], ['P', 667], ['Q', 778], ['R', 722], ['S', 667], ['T', 611], ['U', 722], ['V', 667], ['W', 944], ['X', 667], ['Y', 667], ['Z', 611], ['bracketleft', 333], ['backslash', 278], ['bracketright', 333], ['asciicircum', 584], ['underscore', 556], ['quoteleft', 278], ['a', 556], ['b', 611], ['c', 556], ['d', 611], ['e', 556], ['f', 333], ['g', 611], ['h', 611], ['i', 278], ['j', 278], ['k', 556], ['l', 278], ['m', 889], ['n', 611], ['o', 611], ['p', 611], ['q', 611], ['r', 389], ['s', 556], ['t', 333], ['u', 611], ['v', 556], ['w', 778], ['x', 556], ['y', 556], ['z', 500], ['braceleft', 389], ['bar', 280], ['braceright', 389], ['asciitilde', 584], ['exclamdown', 333], ['cent', 556], ['sterling', 556], ['fraction', 167], ['yen', 556], ['florin', 556], ['section', 556], ['currency', 556], ['quotesingle', 238], ['quotedblleft', 500], ['guillemotleft', 556], ['guilsinglleft', 333], ['guilsinglright', 333], ['fi', 611], ['fl', 611], ['endash', 556], ['dagger', 556], ['daggerdbl', 556], ['periodcentered', 278], ['paragraph', 556], ['bullet', 350], ['quotesinglbase', 278], ['quotedblbase', 500], ['quotedblright', 500], ['guillemotright', 556], ['ellipsis', 1000], ['perthousand', 1000], ['questiondown', 611], ['grave', 333], ['acute', 333], ['circumflex', 333], ['tilde', 333], ['macron', 333], ['breve', 333], ['dotaccent', 333], ['dieresis', 333], ['ring', 333], ['cedilla', 333], ['hungarumlaut', 333], ['ogonek', 333], ['caron', 333], ['emdash', 1000], ['AE', 1000], ['ordfeminine', 370], ['Lslash', 611], ['Oslash', 778], ['OE', 1000], ['ordmasculine', 365], ['ae', 889], ['dotlessi', 278], ['lslash', 278], ['oslash', 611], ['oe', 944], ['germandbls', 611], ['Idieresis', 278], ['eacute', 556], ['abreve', 556], ['uhungarumlaut', 611], ['ecaron', 556], ['Ydieresis', 667], ['divide', 584], ['Yacute', 667], ['Acircumflex', 722], ['aacute', 556], ['Ucircumflex', 722], ['yacute', 556], ['scommaaccent', 556], ['ecircumflex', 556], ['Uring', 722], ['Udieresis', 722], ['aogonek', 556], ['Uacute', 722], ['uogonek', 611], ['Edieresis', 667], ['Dcroat', 722], ['commaaccent', 250], ['copyright', 737], ['Emacron', 667], ['ccaron', 556], ['aring', 556], ['Ncommaaccent', 722], ['lacute', 278], ['agrave', 556], ['Tcommaaccent', 611], ['Cacute', 722], ['atilde', 556], ['Edotaccent', 667], ['scaron', 556], ['scedilla', 556], ['iacute', 278], ['lozenge', 494], ['Rcaron', 722], ['Gcommaaccent', 778], ['ucircumflex', 611], ['acircumflex', 556], ['Amacron', 722], ['rcaron', 389], ['ccedilla', 556], ['Zdotaccent', 611], ['Thorn', 667], ['Omacron', 778], ['Racute', 722], ['Sacute', 667], ['dcaron', 743], ['Umacron', 722], ['uring', 611], ['threesuperior', 333], ['Ograve', 778], ['Agrave', 722], ['Abreve', 722], ['multiply', 584], ['uacute', 611], ['Tcaron', 611], ['partialdiff', 494], ['ydieresis', 556], ['Nacute', 722], ['icircumflex', 278], ['Ecircumflex', 667], ['adieresis', 556], ['edieresis', 556], ['cacute', 556], ['nacute', 611], ['umacron', 611], ['Ncaron', 722], ['Iacute', 278], ['plusminus', 584], ['brokenbar', 280], ['registered', 737], ['Gbreve', 778], ['Idotaccent', 278], ['summation', 600], ['Egrave', 667], ['racute', 389], ['omacron', 611], ['Zacute', 611], ['Zcaron', 611], ['greaterequal', 549], ['Eth', 722], ['Ccedilla', 722], ['lcommaaccent', 278], ['tcaron', 389], ['eogonek', 556], ['Uogonek', 722], ['Aacute', 722], ['Adieresis', 722], ['egrave', 556], ['zacute', 500], ['iogonek', 278], ['Oacute', 778], ['oacute', 611], ['amacron', 556], ['sacute', 556], ['idieresis', 278], ['Ocircumflex', 778], ['Ugrave', 722], ['Delta', 612], ['thorn', 611], ['twosuperior', 333], ['Odieresis', 778], ['mu', 611], ['igrave', 278], ['ohungarumlaut', 611], ['Eogonek', 667], ['dcroat', 611], ['threequarters', 834], ['Scedilla', 667], ['lcaron', 400], ['Kcommaaccent', 722], ['Lacute', 611], ['trademark', 1000], ['edotaccent', 556], ['Igrave', 278], ['Imacron', 278], ['Lcaron', 611], ['onehalf', 834], ['lessequal', 549], ['ocircumflex', 611], ['ntilde', 611], ['Uhungarumlaut', 722], ['Eacute', 667], ['emacron', 556], ['gbreve', 611], ['onequarter', 834], ['Scaron', 667], ['Scommaaccent', 667], ['Ohungarumlaut', 778], ['degree', 400], ['ograve', 611], ['Ccaron', 722], ['ugrave', 611], ['radical', 549], ['Dcaron', 722], ['rcommaaccent', 389], ['Ntilde', 722], ['otilde', 611], ['Rcommaaccent', 722], ['Lcommaaccent', 611], ['Atilde', 722], ['Aogonek', 722], ['Aring', 722], ['Otilde', 778], ['zdotaccent', 500], ['Ecaron', 667], ['Iogonek', 278], ['kcommaaccent', 556], ['minus', 584], ['Icircumflex', 278], ['ncaron', 611], ['tcommaaccent', 333], ['logicalnot', 584], ['odieresis', 611], ['udieresis', 611], ['notequal', 549], ['gcommaaccent', 611], ['eth', 611], ['zcaron', 500], ['ncommaaccent', 611], ['onesuperior', 333], ['imacron', 278], ['Euro', 556],
  ])], ['Helvetica-BoldOblique',new Map<string,number> ([
    ['space', 278], ['exclam', 333], ['quotedbl', 474], ['numbersign', 556], ['dollar', 556], ['percent', 889], ['ampersand', 722], ['quoteright', 278], ['parenleft', 333], ['parenright', 333], ['asterisk', 389], ['plus', 584], ['comma', 278], ['hyphen', 333], ['period', 278], ['slash', 278], ['zero', 556], ['one', 556], ['two', 556], ['three', 556], ['four', 556], ['five', 556], ['six', 556], ['seven', 556], ['eight', 556], ['nine', 556], ['colon', 333], ['semicolon', 333], ['less', 584], ['equal', 584], ['greater', 584], ['question', 611], ['at', 975], ['A', 722], ['B', 722], ['C', 722], ['D', 722], ['E', 667], ['F', 611], ['G', 778], ['H', 722], ['I', 278], ['J', 556], ['K', 722], ['L', 611], ['M', 833], ['N', 722], ['O', 778], ['P', 667], ['Q', 778], ['R', 722], ['S', 667], ['T', 611], ['U', 722], ['V', 667], ['W', 944], ['X', 667], ['Y', 667], ['Z', 611], ['bracketleft', 333], ['backslash', 278], ['bracketright', 333], ['asciicircum', 584], ['underscore', 556], ['quoteleft', 278], ['a', 556], ['b', 611], ['c', 556], ['d', 611], ['e', 556], ['f', 333], ['g', 611], ['h', 611], ['i', 278], ['j', 278], ['k', 556], ['l', 278], ['m', 889], ['n', 611], ['o', 611], ['p', 611], ['q', 611], ['r', 389], ['s', 556], ['t', 333], ['u', 611], ['v', 556], ['w', 778], ['x', 556], ['y', 556], ['z', 500], ['braceleft', 389], ['bar', 280], ['braceright', 389], ['asciitilde', 584], ['exclamdown', 333], ['cent', 556], ['sterling', 556], ['fraction', 167], ['yen', 556], ['florin', 556], ['section', 556], ['currency', 556], ['quotesingle', 238], ['quotedblleft', 500], ['guillemotleft', 556], ['guilsinglleft', 333], ['guilsinglright', 333], ['fi', 611], ['fl', 611], ['endash', 556], ['dagger', 556], ['daggerdbl', 556], ['periodcentered', 278], ['paragraph', 556], ['bullet', 350], ['quotesinglbase', 278], ['quotedblbase', 500], ['quotedblright', 500], ['guillemotright', 556], ['ellipsis', 1000], ['perthousand', 1000], ['questiondown', 611], ['grave', 333], ['acute', 333], ['circumflex', 333], ['tilde', 333], ['macron', 333], ['breve', 333], ['dotaccent', 333], ['dieresis', 333], ['ring', 333], ['cedilla', 333], ['hungarumlaut', 333], ['ogonek', 333], ['caron', 333], ['emdash', 1000], ['AE', 1000], ['ordfeminine', 370], ['Lslash', 611], ['Oslash', 778], ['OE', 1000], ['ordmasculine', 365], ['ae', 889], ['dotlessi', 278], ['lslash', 278], ['oslash', 611], ['oe', 944], ['germandbls', 611], ['Idieresis', 278], ['eacute', 556], ['abreve', 556], ['uhungarumlaut', 611], ['ecaron', 556], ['Ydieresis', 667], ['divide', 584], ['Yacute', 667], ['Acircumflex', 722], ['aacute', 556], ['Ucircumflex', 722], ['yacute', 556], ['scommaaccent', 556], ['ecircumflex', 556], ['Uring', 722], ['Udieresis', 722], ['aogonek', 556], ['Uacute', 722], ['uogonek', 611], ['Edieresis', 667], ['Dcroat', 722], ['commaaccent', 250], ['copyright', 737], ['Emacron', 667], ['ccaron', 556], ['aring', 556], ['Ncommaaccent', 722], ['lacute', 278], ['agrave', 556], ['Tcommaaccent', 611], ['Cacute', 722], ['atilde', 556], ['Edotaccent', 667], ['scaron', 556], ['scedilla', 556], ['iacute', 278], ['lozenge', 494], ['Rcaron', 722], ['Gcommaaccent', 778], ['ucircumflex', 611], ['acircumflex', 556], ['Amacron', 722], ['rcaron', 389], ['ccedilla', 556], ['Zdotaccent', 611], ['Thorn', 667], ['Omacron', 778], ['Racute', 722], ['Sacute', 667], ['dcaron', 743], ['Umacron', 722], ['uring', 611], ['threesuperior', 333], ['Ograve', 778], ['Agrave', 722], ['Abreve', 722], ['multiply', 584], ['uacute', 611], ['Tcaron', 611], ['partialdiff', 494], ['ydieresis', 556], ['Nacute', 722], ['icircumflex', 278], ['Ecircumflex', 667], ['adieresis', 556], ['edieresis', 556], ['cacute', 556], ['nacute', 611], ['umacron', 611], ['Ncaron', 722], ['Iacute', 278], ['plusminus', 584], ['brokenbar', 280], ['registered', 737], ['Gbreve', 778], ['Idotaccent', 278], ['summation', 600], ['Egrave', 667], ['racute', 389], ['omacron', 611], ['Zacute', 611], ['Zcaron', 611], ['greaterequal', 549], ['Eth', 722], ['Ccedilla', 722], ['lcommaaccent', 278], ['tcaron', 389], ['eogonek', 556], ['Uogonek', 722], ['Aacute', 722], ['Adieresis', 722], ['egrave', 556], ['zacute', 500], ['iogonek', 278], ['Oacute', 778], ['oacute', 611], ['amacron', 556], ['sacute', 556], ['idieresis', 278], ['Ocircumflex', 778], ['Ugrave', 722], ['Delta', 612], ['thorn', 611], ['twosuperior', 333], ['Odieresis', 778], ['mu', 611], ['igrave', 278], ['ohungarumlaut', 611], ['Eogonek', 667], ['dcroat', 611], ['threequarters', 834], ['Scedilla', 667], ['lcaron', 400], ['Kcommaaccent', 722], ['Lacute', 611], ['trademark', 1000], ['edotaccent', 556], ['Igrave', 278], ['Imacron', 278], ['Lcaron', 611], ['onehalf', 834], ['lessequal', 549], ['ocircumflex', 611], ['ntilde', 611], ['Uhungarumlaut', 722], ['Eacute', 667], ['emacron', 556], ['gbreve', 611], ['onequarter', 834], ['Scaron', 667], ['Scommaaccent', 667], ['Ohungarumlaut', 778], ['degree', 400], ['ograve', 611], ['Ccaron', 722], ['ugrave', 611], ['radical', 549], ['Dcaron', 722], ['rcommaaccent', 389], ['Ntilde', 722], ['otilde', 611], ['Rcommaaccent', 722], ['Lcommaaccent', 611], ['Atilde', 722], ['Aogonek', 722], ['Aring', 722], ['Otilde', 778], ['zdotaccent', 500], ['Ecaron', 667], ['Iogonek', 278], ['kcommaaccent', 556], ['minus', 584], ['Icircumflex', 278], ['ncaron', 611], ['tcommaaccent', 333], ['logicalnot', 584], ['odieresis', 611], ['udieresis', 611], ['notequal', 549], ['gcommaaccent', 611], ['eth', 611], ['zcaron', 500], ['ncommaaccent', 611], ['onesuperior', 333], ['imacron', 278], ['Euro', 556],
  ])], ['Helvetica-Oblique', new Map<string, number>([
    ['space', 278], ['exclam', 278], ['quotedbl', 355], ['numbersign', 556], ['dollar', 556], ['percent', 889], ['ampersand', 667], ['quoteright', 222], ['parenleft', 333], ['parenright', 333], ['asterisk', 389], ['plus', 584], ['comma', 278], ['hyphen', 333], ['period', 278], ['slash', 278], ['zero', 556], ['one', 556], ['two', 556], ['three', 556], ['four', 556], ['five', 556], ['six', 556], ['seven', 556], ['eight', 556], ['nine', 556], ['colon', 278], ['semicolon', 278], ['less', 584], ['equal', 584], ['greater', 584], ['question', 556], ['at', 1015], ['A', 667], ['B', 667], ['C', 722], ['D', 722], ['E', 667], ['F', 611], ['G', 778], ['H', 722], ['I', 278], ['J', 500], ['K', 667], ['L', 556], ['M', 833], ['N', 722], ['O', 778], ['P', 667], ['Q', 778], ['R', 722], ['S', 667], ['T', 611], ['U', 722], ['V', 667], ['W', 944], ['X', 667], ['Y', 667], ['Z', 611], ['bracketleft', 278], ['backslash', 278], ['bracketright', 278], ['asciicircum', 469], ['underscore', 556], ['quoteleft', 222], ['a', 556], ['b', 556], ['c', 500], ['d', 556], ['e', 556], ['f', 278], ['g', 556], ['h', 556], ['i', 222], ['j', 222], ['k', 500], ['l', 222], ['m', 833], ['n', 556], ['o', 556], ['p', 556], ['q', 556], ['r', 333], ['s', 500], ['t', 278], ['u', 556], ['v', 500], ['w', 722], ['x', 500], ['y', 500], ['z', 500], ['braceleft', 334], ['bar', 260], ['braceright', 334], ['asciitilde', 584], ['exclamdown', 333], ['cent', 556], ['sterling', 556], ['fraction', 167], ['yen', 556], ['florin', 556], ['section', 556], ['currency', 556], ['quotesingle', 191], ['quotedblleft', 333], ['guillemotleft', 556], ['guilsinglleft', 333], ['guilsinglright', 333], ['fi', 500], ['fl', 500], ['endash', 556], ['dagger', 556], ['daggerdbl', 556], ['periodcentered', 278], ['paragraph', 537], ['bullet', 350], ['quotesinglbase', 222], ['quotedblbase', 333], ['quotedblright', 333], ['guillemotright', 556], ['ellipsis', 1000], ['perthousand', 1000], ['questiondown', 611], ['grave', 333], ['acute', 333], ['circumflex', 333], ['tilde', 333], ['macron', 333], ['breve', 333], ['dotaccent', 333], ['dieresis', 333], ['ring', 333], ['cedilla', 333], ['hungarumlaut', 333], ['ogonek', 333], ['caron', 333], ['emdash', 1000], ['AE', 1000], ['ordfeminine', 370], ['Lslash', 556], ['Oslash', 778], ['OE', 1000], ['ordmasculine', 365], ['ae', 889], ['dotlessi', 278], ['lslash', 222], ['oslash', 611], ['oe', 944], ['germandbls', 611], ['Idieresis', 278], ['eacute', 556], ['abreve', 556], ['uhungarumlaut', 556], ['ecaron', 556], ['Ydieresis', 667], ['divide', 584], ['Yacute', 667], ['Acircumflex', 667], ['aacute', 556], ['Ucircumflex', 722], ['yacute', 500], ['scommaaccent', 500], ['ecircumflex', 556], ['Uring', 722], ['Udieresis', 722], ['aogonek', 556], ['Uacute', 722], ['uogonek', 556], ['Edieresis', 667], ['Dcroat', 722], ['commaaccent', 250], ['copyright', 737], ['Emacron', 667], ['ccaron', 500], ['aring', 556], ['Ncommaaccent', 722], ['lacute', 222], ['agrave', 556], ['Tcommaaccent', 611], ['Cacute', 722], ['atilde', 556], ['Edotaccent', 667], ['scaron', 500], ['scedilla', 500], ['iacute', 278], ['lozenge', 471], ['Rcaron', 722], ['Gcommaaccent', 778], ['ucircumflex', 556], ['acircumflex', 556], ['Amacron', 667], ['rcaron', 333], ['ccedilla', 500], ['Zdotaccent', 611], ['Thorn', 667], ['Omacron', 778], ['Racute', 722], ['Sacute', 667], ['dcaron', 643], ['Umacron', 722], ['uring', 556], ['threesuperior', 333], ['Ograve', 778], ['Agrave', 667], ['Abreve', 667], ['multiply', 584], ['uacute', 556], ['Tcaron', 611], ['partialdiff', 476], ['ydieresis', 500], ['Nacute', 722], ['icircumflex', 278], ['Ecircumflex', 667], ['adieresis', 556], ['edieresis', 556], ['cacute', 500], ['nacute', 556], ['umacron', 556], ['Ncaron', 722], ['Iacute', 278], ['plusminus', 584], ['brokenbar', 260], ['registered', 737], ['Gbreve', 778], ['Idotaccent', 278], ['summation', 600], ['Egrave', 667], ['racute', 333], ['omacron', 556], ['Zacute', 611], ['Zcaron', 611], ['greaterequal', 549], ['Eth', 722], ['Ccedilla', 722], ['lcommaaccent', 222], ['tcaron', 317], ['eogonek', 556], ['Uogonek', 722], ['Aacute', 667], ['Adieresis', 667], ['egrave', 556], ['zacute', 500], ['iogonek', 222], ['Oacute', 778], ['oacute', 556], ['amacron', 556], ['sacute', 500], ['idieresis', 278], ['Ocircumflex', 778], ['Ugrave', 722], ['Delta', 612], ['thorn', 556], ['twosuperior', 333], ['Odieresis', 778], ['mu', 556], ['igrave', 278], ['ohungarumlaut', 556], ['Eogonek', 667], ['dcroat', 556], ['threequarters', 834], ['Scedilla', 667], ['lcaron', 299], ['Kcommaaccent', 667], ['Lacute', 556], ['trademark', 1000], ['edotaccent', 556], ['Igrave', 278], ['Imacron', 278], ['Lcaron', 556], ['onehalf', 834], ['lessequal', 549], ['ocircumflex', 556], ['ntilde', 556], ['Uhungarumlaut', 722], ['Eacute', 667], ['emacron', 556], ['gbreve', 556], ['onequarter', 834], ['Scaron', 667], ['Scommaaccent', 667], ['Ohungarumlaut', 778], ['degree', 400], ['ograve', 556], ['Ccaron', 722], ['ugrave', 556], ['radical', 453], ['Dcaron', 722], ['rcommaaccent', 333], ['Ntilde', 722], ['otilde', 556], ['Rcommaaccent', 722], ['Lcommaaccent', 556], ['Atilde', 667], ['Aogonek', 667], ['Aring', 667], ['Otilde', 778], ['zdotaccent', 500], ['Ecaron', 667], ['Iogonek', 278], ['kcommaaccent', 500], ['minus', 584], ['Icircumflex', 278], ['ncaron', 556], ['tcommaaccent', 278], ['logicalnot', 584], ['odieresis', 556], ['udieresis', 556], ['notequal', 549], ['gcommaaccent', 556], ['eth', 556], ['zcaron', 500], ['ncommaaccent', 556], ['onesuperior', 333], ['imacron', 278], ['Euro', 556],


  ])], ['Symbol', new Map<string, number>([
    ['space', 250], ['exclam', 333], ['universal', 713], ['numbersign', 500], ['existential', 549], ['percent', 833], ['ampersand', 778], ['suchthat', 439], ['parenleft', 333], ['parenright', 333], ['asteriskmath', 500], ['plus', 549], ['comma', 250], ['minus', 549], ['period', 250], ['slash', 278], ['zero', 500], ['one', 500], ['two', 500], ['three', 500], ['four', 500], ['five', 500], ['six', 500], ['seven', 500], ['eight', 500], ['nine', 500], ['colon', 278], ['semicolon', 278], ['less', 549], ['equal', 549], ['greater', 549], ['question', 444], ['congruent', 549], ['Alpha', 722], ['Beta', 667], ['Chi', 722], ['Delta', 612], ['Epsilon', 611], ['Phi', 763], ['Gamma', 603], ['Eta', 722], ['Iota', 333], ['theta1', 631], ['Kappa', 722], ['Lambda', 686], ['Mu', 889], ['Nu', 722], ['Omicron', 722], ['Pi', 768], ['Theta', 741], ['Rho', 556], ['Sigma', 592], ['Tau', 611], ['Upsilon', 690], ['sigma1', 439], ['Omega', 768], ['Xi', 645], ['Psi', 795], ['Zeta', 611], ['bracketleft', 333], ['therefore', 863], ['bracketright', 333], ['perpendicular', 658], ['underscore', 500], ['radicalex', 500], ['alpha', 631], ['beta', 549], ['chi', 549], ['delta', 494], ['epsilon', 439], ['phi', 521], ['gamma', 411], ['eta', 603], ['iota', 329], ['phi1', 603], ['kappa', 549], ['lambda', 549], ['mu', 576], ['nu', 521], ['omicron', 549], ['pi', 549], ['theta', 521], ['rho', 549], ['sigma', 603], ['tau', 439], ['upsilon', 576], ['omega1', 713], ['omega', 686], ['xi', 493], ['psi', 686], ['zeta', 494], ['braceleft', 480], ['bar', 200], ['braceright', 480], ['similar', 549], ['Euro', 750], ['Upsilon1', 620], ['minute', 247], ['lessequal', 549], ['fraction', 167], ['infinity', 713], ['florin', 500], ['club', 753], ['diamond', 753], ['heart', 753], ['spade', 753], ['arrowboth', 1042], ['arrowleft', 987], ['arrowup', 603], ['arrowright', 987], ['arrowdown', 603], ['degree', 400], ['plusminus', 549], ['second', 411], ['greaterequal', 549], ['multiply', 549], ['proportional', 713], ['partialdiff', 494], ['bullet', 460], ['divide', 549], ['notequal', 549], ['equivalence', 549], ['approxequal', 549], ['ellipsis', 1000], ['arrowvertex', 603], ['arrowhorizex', 1000], ['carriagereturn', 658], ['aleph', 823], ['Ifraktur', 686], ['Rfraktur', 795], ['weierstrass', 987], ['circlemultiply', 768], ['circleplus', 768], ['emptyset', 823], ['intersection', 768], ['union', 768], ['propersuperset', 713], ['reflexsuperset', 713], ['notsubset', 713], ['propersubset', 713], ['reflexsubset', 713], ['element', 713], ['notelement', 713], ['angle', 768], ['gradient', 713], ['registerserif', 790], ['copyrightserif', 790], ['trademarkserif', 890], ['product', 823], ['radical', 549], ['dotmath', 250], ['logicalnot', 713], ['logicaland', 603], ['logicalor', 603], ['arrowdblboth', 1042], ['arrowdblleft', 987], ['arrowdblup', 603], ['arrowdblright', 987], ['arrowdbldown', 603], ['lozenge', 494], ['angleleft', 329], ['registersans', 790], ['copyrightsans', 790], ['trademarksans', 786], ['summation', 713], ['parenlefttp', 384], ['parenleftex', 384], ['parenleftbt', 384], ['bracketlefttp', 384], ['bracketleftex', 384], ['bracketleftbt', 384], ['bracelefttp', 494], ['braceleftmid', 494], ['braceleftbt', 494], ['braceex', 494], ['angleright', 329], ['integral', 274], ['integraltp', 686], ['integralex', 686], ['integralbt', 686], ['parenrighttp', 384], ['parenrightex', 384], ['parenrightbt', 384], ['bracketrighttp', 384], ['bracketrightex', 384], ['bracketrightbt', 384], ['bracerighttp', 494], ['bracerightmid', 494], ['bracerightbt', 494], ['apple', 790],

  ])], ['Times-Roman', new Map<string, number>([
    ['space', 250], ['exclam', 333], ['quotedbl', 408], ['numbersign', 500], ['dollar', 500], ['percent', 833], ['ampersand', 778], ['quoteright', 333], ['parenleft', 333], ['parenright', 333], ['asterisk', 500], ['plus', 564], ['comma', 250], ['hyphen', 333], ['period', 250], ['slash', 278], ['zero', 500], ['one', 500], ['two', 500], ['three', 500], ['four', 500], ['five', 500], ['six', 500], ['seven', 500], ['eight', 500], ['nine', 500], ['colon', 278], ['semicolon', 278], ['less', 564], ['equal', 564], ['greater', 564], ['question', 444], ['at', 921], ['A', 722], ['B', 667], ['C', 667], ['D', 722], ['E', 611], ['F', 556], ['G', 722], ['H', 722], ['I', 333], ['J', 389], ['K', 722], ['L', 611], ['M', 889], ['N', 722], ['O', 722], ['P', 556], ['Q', 722], ['R', 667], ['S', 556], ['T', 611], ['U', 722], ['V', 722], ['W', 944], ['X', 722], ['Y', 722], ['Z', 611], ['bracketleft', 333], ['backslash', 278], ['bracketright', 333], ['asciicircum', 469], ['underscore', 500], ['quoteleft', 333], ['a', 444], ['b', 500], ['c', 444], ['d', 500], ['e', 444], ['f', 333], ['g', 500], ['h', 500], ['i', 278], ['j', 278], ['k', 500], ['l', 278], ['m', 778], ['n', 500], ['o', 500], ['p', 500], ['q', 500], ['r', 333], ['s', 389], ['t', 278], ['u', 500], ['v', 500], ['w', 722], ['x', 500], ['y', 500], ['z', 444], ['braceleft', 480], ['bar', 200], ['braceright', 480], ['asciitilde', 541], ['exclamdown', 333], ['cent', 500], ['sterling', 500], ['fraction', 167], ['yen', 500], ['florin', 500], ['section', 500], ['currency', 500], ['quotesingle', 180], ['quotedblleft', 444], ['guillemotleft', 500], ['guilsinglleft', 333], ['guilsinglright', 333], ['fi', 556], ['fl', 556], ['endash', 500], ['dagger', 500], ['daggerdbl', 500], ['periodcentered', 250], ['paragraph', 453], ['bullet', 350], ['quotesinglbase', 333], ['quotedblbase', 444], ['quotedblright', 444], ['guillemotright', 500], ['ellipsis', 1000], ['perthousand', 1000], ['questiondown', 444], ['grave', 333], ['acute', 333], ['circumflex', 333], ['tilde', 333], ['macron', 333], ['breve', 333], ['dotaccent', 333], ['dieresis', 333], ['ring', 333], ['cedilla', 333], ['hungarumlaut', 333], ['ogonek', 333], ['caron', 333], ['emdash', 1000], ['AE', 889], ['ordfeminine', 276], ['Lslash', 611], ['Oslash', 722], ['OE', 889], ['ordmasculine', 310], ['ae', 667], ['dotlessi', 278], ['lslash', 278], ['oslash', 500], ['oe', 722], ['germandbls', 500], ['Idieresis', 333], ['eacute', 444], ['abreve', 444], ['uhungarumlaut', 500], ['ecaron', 444], ['Ydieresis', 722], ['divide', 564], ['Yacute', 722], ['Acircumflex', 722], ['aacute', 444], ['Ucircumflex', 722], ['yacute', 500], ['scommaaccent', 389], ['ecircumflex', 444], ['Uring', 722], ['Udieresis', 722], ['aogonek', 444], ['Uacute', 722], ['uogonek', 500], ['Edieresis', 611], ['Dcroat', 722], ['commaaccent', 250], ['copyright', 760], ['Emacron', 611], ['ccaron', 444], ['aring', 444], ['Ncommaaccent', 722], ['lacute', 278], ['agrave', 444], ['Tcommaaccent', 611], ['Cacute', 667], ['atilde', 444], ['Edotaccent', 611], ['scaron', 389], ['scedilla', 389], ['iacute', 278], ['lozenge', 471], ['Rcaron', 667], ['Gcommaaccent', 722], ['ucircumflex', 500], ['acircumflex', 444], ['Amacron', 722], ['rcaron', 333], ['ccedilla', 444], ['Zdotaccent', 611], ['Thorn', 556], ['Omacron', 722], ['Racute', 667], ['Sacute', 556], ['dcaron', 588], ['Umacron', 722], ['uring', 500], ['threesuperior', 300], ['Ograve', 722], ['Agrave', 722], ['Abreve', 722], ['multiply', 564], ['uacute', 500], ['Tcaron', 611], ['partialdiff', 476], ['ydieresis', 500], ['Nacute', 722], ['icircumflex', 278], ['Ecircumflex', 611], ['adieresis', 444], ['edieresis', 444], ['cacute', 444], ['nacute', 500], ['umacron', 500], ['Ncaron', 722], ['Iacute', 333], ['plusminus', 564], ['brokenbar', 200], ['registered', 760], ['Gbreve', 722], ['Idotaccent', 333], ['summation', 600], ['Egrave', 611], ['racute', 333], ['omacron', 500], ['Zacute', 611], ['Zcaron', 611], ['greaterequal', 549], ['Eth', 722], ['Ccedilla', 667], ['lcommaaccent', 278], ['tcaron', 326], ['eogonek', 444], ['Uogonek', 722], ['Aacute', 722], ['Adieresis', 722], ['egrave', 444], ['zacute', 444], ['iogonek', 278], ['Oacute', 722], ['oacute', 500], ['amacron', 444], ['sacute', 389], ['idieresis', 278], ['Ocircumflex', 722], ['Ugrave', 722], ['Delta', 612], ['thorn', 500], ['twosuperior', 300], ['Odieresis', 722], ['mu', 500], ['igrave', 278], ['ohungarumlaut', 500], ['Eogonek', 611], ['dcroat', 500], ['threequarters', 750], ['Scedilla', 556], ['lcaron', 344], ['Kcommaaccent', 722], ['Lacute', 611], ['trademark', 980], ['edotaccent', 444], ['Igrave', 333], ['Imacron', 333], ['Lcaron', 611], ['onehalf', 750], ['lessequal', 549], ['ocircumflex', 500], ['ntilde', 500], ['Uhungarumlaut', 722], ['Eacute', 611], ['emacron', 444], ['gbreve', 500], ['onequarter', 750], ['Scaron', 556], ['Scommaaccent', 556], ['Ohungarumlaut', 722], ['degree', 400], ['ograve', 500], ['Ccaron', 667], ['ugrave', 500], ['radical', 453], ['Dcaron', 722], ['rcommaaccent', 333], ['Ntilde', 722], ['otilde', 500], ['Rcommaaccent', 667], ['Lcommaaccent', 611], ['Atilde', 722], ['Aogonek', 722], ['Aring', 722], ['Otilde', 722], ['zdotaccent', 444], ['Ecaron', 611], ['Iogonek', 333], ['kcommaaccent', 500], ['minus', 564], ['Icircumflex', 333], ['ncaron', 500], ['tcommaaccent', 278], ['logicalnot', 564], ['odieresis', 500], ['udieresis', 500], ['notequal', 549], ['gcommaaccent', 500], ['eth', 500], ['zcaron', 444], ['ncommaaccent', 500], ['onesuperior', 300], ['imacron', 278], ['Euro', 500],
  ])], ['Times-Bold', new Map<string, number>([
    ['space', 250], ['exclam', 333], ['quotedbl', 555], ['numbersign', 500], ['dollar', 500], ['percent', 1000], ['ampersand', 833], ['quoteright', 333], ['parenleft', 333], ['parenright', 333], ['asterisk', 500], ['plus', 570], ['comma', 250], ['hyphen', 333], ['period', 250], ['slash', 278], ['zero', 500], ['one', 500], ['two', 500], ['three', 500], ['four', 500], ['five', 500], ['six', 500], ['seven', 500], ['eight', 500], ['nine', 500], ['colon', 333], ['semicolon', 333], ['less', 570], ['equal', 570], ['greater', 570], ['question', 500], ['at', 930], ['A', 722], ['B', 667], ['C', 722], ['D', 722], ['E', 667], ['F', 611], ['G', 778], ['H', 778], ['I', 389], ['J', 500], ['K', 778], ['L', 667], ['M', 944], ['N', 722], ['O', 778], ['P', 611], ['Q', 778], ['R', 722], ['S', 556], ['T', 667], ['U', 722], ['V', 722], ['W', 1000], ['X', 722], ['Y', 722], ['Z', 667], ['bracketleft', 333], ['backslash', 278], ['bracketright', 333], ['asciicircum', 581], ['underscore', 500], ['quoteleft', 333], ['a', 500], ['b', 556], ['c', 444], ['d', 556], ['e', 444], ['f', 333], ['g', 500], ['h', 556], ['i', 278], ['j', 333], ['k', 556], ['l', 278], ['m', 833], ['n', 556], ['o', 500], ['p', 556], ['q', 556], ['r', 444], ['s', 389], ['t', 333], ['u', 556], ['v', 500], ['w', 722], ['x', 500], ['y', 500], ['z', 444], ['braceleft', 394], ['bar', 220], ['braceright', 394], ['asciitilde', 520], ['exclamdown', 333], ['cent', 500], ['sterling', 500], ['fraction', 167], ['yen', 500], ['florin', 500], ['section', 500], ['currency', 500], ['quotesingle', 278], ['quotedblleft', 500], ['guillemotleft', 500], ['guilsinglleft', 333], ['guilsinglright', 333], ['fi', 556], ['fl', 556], ['endash', 500], ['dagger', 500], ['daggerdbl', 500], ['periodcentered', 250], ['paragraph', 540], ['bullet', 350], ['quotesinglbase', 333], ['quotedblbase', 500], ['quotedblright', 500], ['guillemotright', 500], ['ellipsis', 1000], ['perthousand', 1000], ['questiondown', 500], ['grave', 333], ['acute', 333], ['circumflex', 333], ['tilde', 333], ['macron', 333], ['breve', 333], ['dotaccent', 333], ['dieresis', 333], ['ring', 333], ['cedilla', 333], ['hungarumlaut', 333], ['ogonek', 333], ['caron', 333], ['emdash', 1000], ['AE', 1000], ['ordfeminine', 300], ['Lslash', 667], ['Oslash', 778], ['OE', 1000], ['ordmasculine', 330], ['ae', 722], ['dotlessi', 278], ['lslash', 278], ['oslash', 500], ['oe', 722], ['germandbls', 556], ['Idieresis', 389], ['eacute', 444], ['abreve', 500], ['uhungarumlaut', 556], ['ecaron', 444], ['Ydieresis', 722], ['divide', 570], ['Yacute', 722], ['Acircumflex', 722], ['aacute', 500], ['Ucircumflex', 722], ['yacute', 500], ['scommaaccent', 389], ['ecircumflex', 444], ['Uring', 722], ['Udieresis', 722], ['aogonek', 500], ['Uacute', 722], ['uogonek', 556], ['Edieresis', 667], ['Dcroat', 722], ['commaaccent', 250], ['copyright', 747], ['Emacron', 667], ['ccaron', 444], ['aring', 500], ['Ncommaaccent', 722], ['lacute', 278], ['agrave', 500], ['Tcommaaccent', 667], ['Cacute', 722], ['atilde', 500], ['Edotaccent', 667], ['scaron', 389], ['scedilla', 389], ['iacute', 278], ['lozenge', 494], ['Rcaron', 722], ['Gcommaaccent', 778], ['ucircumflex', 556], ['acircumflex', 500], ['Amacron', 722], ['rcaron', 444], ['ccedilla', 444], ['Zdotaccent', 667], ['Thorn', 611], ['Omacron', 778], ['Racute', 722], ['Sacute', 556], ['dcaron', 672], ['Umacron', 722], ['uring', 556], ['threesuperior', 300], ['Ograve', 778], ['Agrave', 722], ['Abreve', 722], ['multiply', 570], ['uacute', 556], ['Tcaron', 667], ['partialdiff', 494], ['ydieresis', 500], ['Nacute', 722], ['icircumflex', 278], ['Ecircumflex', 667], ['adieresis', 500], ['edieresis', 444], ['cacute', 444], ['nacute', 556], ['umacron', 556], ['Ncaron', 722], ['Iacute', 389], ['plusminus', 570], ['brokenbar', 220], ['registered', 747], ['Gbreve', 778], ['Idotaccent', 389], ['summation', 600], ['Egrave', 667], ['racute', 444], ['omacron', 500], ['Zacute', 667], ['Zcaron', 667], ['greaterequal', 549], ['Eth', 722], ['Ccedilla', 722], ['lcommaaccent', 278], ['tcaron', 416], ['eogonek', 444], ['Uogonek', 722], ['Aacute', 722], ['Adieresis', 722], ['egrave', 444], ['zacute', 444], ['iogonek', 278], ['Oacute', 778], ['oacute', 500], ['amacron', 500], ['sacute', 389], ['idieresis', 278], ['Ocircumflex', 778], ['Ugrave', 722], ['Delta', 612], ['thorn', 556], ['twosuperior', 300], ['Odieresis', 778], ['mu', 556], ['igrave', 278], ['ohungarumlaut', 500], ['Eogonek', 667], ['dcroat', 556], ['threequarters', 750], ['Scedilla', 556], ['lcaron', 394], ['Kcommaaccent', 778], ['Lacute', 667], ['trademark', 1000], ['edotaccent', 444], ['Igrave', 389], ['Imacron', 389], ['Lcaron', 667], ['onehalf', 750], ['lessequal', 549], ['ocircumflex', 500], ['ntilde', 556], ['Uhungarumlaut', 722], ['Eacute', 667], ['emacron', 444], ['gbreve', 500], ['onequarter', 750], ['Scaron', 556], ['Scommaaccent', 556], ['Ohungarumlaut', 778], ['degree', 400], ['ograve', 500], ['Ccaron', 722], ['ugrave', 556], ['radical', 549], ['Dcaron', 722], ['rcommaaccent', 444], ['Ntilde', 722], ['otilde', 500], ['Rcommaaccent', 722], ['Lcommaaccent', 667], ['Atilde', 722], ['Aogonek', 722], ['Aring', 722], ['Otilde', 778], ['zdotaccent', 444], ['Ecaron', 667], ['Iogonek', 389], ['kcommaaccent', 556], ['minus', 570], ['Icircumflex', 389], ['ncaron', 556], ['tcommaaccent', 333], ['logicalnot', 570], ['odieresis', 500], ['udieresis', 556], ['notequal', 549], ['gcommaaccent', 500], ['eth', 500], ['zcaron', 444], ['ncommaaccent', 556], ['onesuperior', 300], ['imacron', 278], ['Euro', 500],
  ])], ['Times-BoldItalic', new Map<string, number>([
    ['space', 250], ['exclam', 389], ['quotedbl', 555], ['numbersign', 500], ['dollar', 500], ['percent', 833], ['ampersand', 778], ['quoteright', 333], ['parenleft', 333], ['parenright', 333], ['asterisk', 500], ['plus', 570], ['comma', 250], ['hyphen', 333], ['period', 250], ['slash', 278], ['zero', 500], ['one', 500], ['two', 500], ['three', 500], ['four', 500], ['five', 500], ['six', 500], ['seven', 500], ['eight', 500], ['nine', 500], ['colon', 333], ['semicolon', 333], ['less', 570], ['equal', 570], ['greater', 570], ['question', 500], ['at', 832], ['A', 667], ['B', 667], ['C', 667], ['D', 722], ['E', 667], ['F', 667], ['G', 722], ['H', 778], ['I', 389], ['J', 500], ['K', 667], ['L', 611], ['M', 889], ['N', 722], ['O', 722], ['P', 611], ['Q', 722], ['R', 667], ['S', 556], ['T', 611], ['U', 722], ['V', 667], ['W', 889], ['X', 667], ['Y', 611], ['Z', 611], ['bracketleft', 333], ['backslash', 278], ['bracketright', 333], ['asciicircum', 570], ['underscore', 500], ['quoteleft', 333], ['a', 500], ['b', 500], ['c', 444], ['d', 500], ['e', 444], ['f', 333], ['g', 500], ['h', 556], ['i', 278], ['j', 278], ['k', 500], ['l', 278], ['m', 778], ['n', 556], ['o', 500], ['p', 500], ['q', 500], ['r', 389], ['s', 389], ['t', 278], ['u', 556], ['v', 444], ['w', 667], ['x', 500], ['y', 444], ['z', 389], ['braceleft', 348], ['bar', 220], ['braceright', 348], ['asciitilde', 570], ['exclamdown', 389], ['cent', 500], ['sterling', 500], ['fraction', 167], ['yen', 500], ['florin', 500], ['section', 500], ['currency', 500], ['quotesingle', 278], ['quotedblleft', 500], ['guillemotleft', 500], ['guilsinglleft', 333], ['guilsinglright', 333], ['fi', 556], ['fl', 556], ['endash', 500], ['dagger', 500], ['daggerdbl', 500], ['periodcentered', 250], ['paragraph', 500], ['bullet', 350], ['quotesinglbase', 333], ['quotedblbase', 500], ['quotedblright', 500], ['guillemotright', 500], ['ellipsis', 1000], ['perthousand', 1000], ['questiondown', 500], ['grave', 333], ['acute', 333], ['circumflex', 333], ['tilde', 333], ['macron', 333], ['breve', 333], ['dotaccent', 333], ['dieresis', 333], ['ring', 333], ['cedilla', 333], ['hungarumlaut', 333], ['ogonek', 333], ['caron', 333], ['emdash', 1000], ['AE', 944], ['ordfeminine', 266], ['Lslash', 611], ['Oslash', 722], ['OE', 944], ['ordmasculine', 300], ['ae', 722], ['dotlessi', 278], ['lslash', 278], ['oslash', 500], ['oe', 722], ['germandbls', 500], ['Idieresis', 389], ['eacute', 444], ['abreve', 500], ['uhungarumlaut', 556], ['ecaron', 444], ['Ydieresis', 611], ['divide', 570], ['Yacute', 611], ['Acircumflex', 667], ['aacute', 500], ['Ucircumflex', 722], ['yacute', 444], ['scommaaccent', 389], ['ecircumflex', 444], ['Uring', 722], ['Udieresis', 722], ['aogonek', 500], ['Uacute', 722], ['uogonek', 556], ['Edieresis', 667], ['Dcroat', 722], ['commaaccent', 250], ['copyright', 747], ['Emacron', 667], ['ccaron', 444], ['aring', 500], ['Ncommaaccent', 722], ['lacute', 278], ['agrave', 500], ['Tcommaaccent', 611], ['Cacute', 667], ['atilde', 500], ['Edotaccent', 667], ['scaron', 389], ['scedilla', 389], ['iacute', 278], ['lozenge', 494], ['Rcaron', 667], ['Gcommaaccent', 722], ['ucircumflex', 556], ['acircumflex', 500], ['Amacron', 667], ['rcaron', 389], ['ccedilla', 444], ['Zdotaccent', 611], ['Thorn', 611], ['Omacron', 722], ['Racute', 667], ['Sacute', 556], ['dcaron', 608], ['Umacron', 722], ['uring', 556], ['threesuperior', 300], ['Ograve', 722], ['Agrave', 667], ['Abreve', 667], ['multiply', 570], ['uacute', 556], ['Tcaron', 611], ['partialdiff', 494], ['ydieresis', 444], ['Nacute', 722], ['icircumflex', 278], ['Ecircumflex', 667], ['adieresis', 500], ['edieresis', 444], ['cacute', 444], ['nacute', 556], ['umacron', 556], ['Ncaron', 722], ['Iacute', 389], ['plusminus', 570], ['brokenbar', 220], ['registered', 747], ['Gbreve', 722], ['Idotaccent', 389], ['summation', 600], ['Egrave', 667], ['racute', 389], ['omacron', 500], ['Zacute', 611], ['Zcaron', 611], ['greaterequal', 549], ['Eth', 722], ['Ccedilla', 667], ['lcommaaccent', 278], ['tcaron', 366], ['eogonek', 444], ['Uogonek', 722], ['Aacute', 667], ['Adieresis', 667], ['egrave', 444], ['zacute', 389], ['iogonek', 278], ['Oacute', 722], ['oacute', 500], ['amacron', 500], ['sacute', 389], ['idieresis', 278], ['Ocircumflex', 722], ['Ugrave', 722], ['Delta', 612], ['thorn', 500], ['twosuperior', 300], ['Odieresis', 722], ['mu', 576], ['igrave', 278], ['ohungarumlaut', 500], ['Eogonek', 667], ['dcroat', 500], ['threequarters', 750], ['Scedilla', 556], ['lcaron', 382], ['Kcommaaccent', 667], ['Lacute', 611], ['trademark', 1000], ['edotaccent', 444], ['Igrave', 389], ['Imacron', 389], ['Lcaron', 611], ['onehalf', 750], ['lessequal', 549], ['ocircumflex', 500], ['ntilde', 556], ['Uhungarumlaut', 722], ['Eacute', 667], ['emacron', 444], ['gbreve', 500], ['onequarter', 750], ['Scaron', 556], ['Scommaaccent', 556], ['Ohungarumlaut', 722], ['degree', 400], ['ograve', 500], ['Ccaron', 667], ['ugrave', 556], ['radical', 549], ['Dcaron', 722], ['rcommaaccent', 389], ['Ntilde', 722], ['otilde', 500], ['Rcommaaccent', 667], ['Lcommaaccent', 611], ['Atilde', 667], ['Aogonek', 667], ['Aring', 667], ['Otilde', 722], ['zdotaccent', 389], ['Ecaron', 667], ['Iogonek', 389], ['kcommaaccent', 500], ['minus', 606], ['Icircumflex', 389], ['ncaron', 556], ['tcommaaccent', 278], ['logicalnot', 606], ['odieresis', 500], ['udieresis', 556], ['notequal', 549], ['gcommaaccent', 500], ['eth', 500], ['zcaron', 389], ['ncommaaccent', 556], ['onesuperior', 300], ['imacron', 278], ['Euro', 500]
  ])], ['Times-Italic', new Map<string, number>([
    ['space', 250], ['exclam', 333], ['quotedbl', 420], ['numbersign', 500], ['dollar', 500], ['percent', 833], ['ampersand', 778], ['quoteright', 333], ['parenleft', 333], ['parenright', 333], ['asterisk', 500], ['plus', 675], ['comma', 250], ['hyphen', 333], ['period', 250], ['slash', 278], ['zero', 500], ['one', 500], ['two', 500], ['three', 500], ['four', 500], ['five', 500], ['six', 500], ['seven', 500], ['eight', 500], ['nine', 500], ['colon', 333], ['semicolon', 333], ['less', 675], ['equal', 675], ['greater', 675], ['question', 500], ['at', 920], ['A', 611], ['B', 611], ['C', 667], ['D', 722], ['E', 611], ['F', 611], ['G', 722], ['H', 722], ['I', 333], ['J', 444], ['K', 667], ['L', 556], ['M', 833], ['N', 667], ['O', 722], ['P', 611], ['Q', 722], ['R', 611], ['S', 500], ['T', 556], ['U', 722], ['V', 611], ['W', 833], ['X', 611], ['Y', 556], ['Z', 556], ['bracketleft', 389], ['backslash', 278], ['bracketright', 389], ['asciicircum', 422], ['underscore', 500], ['quoteleft', 333], ['a', 500], ['b', 500], ['c', 444], ['d', 500], ['e', 444], ['f', 278], ['g', 500], ['h', 500], ['i', 278], ['j', 278], ['k', 444], ['l', 278], ['m', 722], ['n', 500], ['o', 500], ['p', 500], ['q', 500], ['r', 389], ['s', 389], ['t', 278], ['u', 500], ['v', 444], ['w', 667], ['x', 444], ['y', 444], ['z', 389], ['braceleft', 400], ['bar', 275], ['braceright', 400], ['asciitilde', 541], ['exclamdown', 389], ['cent', 500], ['sterling', 500], ['fraction', 167], ['yen', 500], ['florin', 500], ['section', 500], ['currency', 500], ['quotesingle', 214], ['quotedblleft', 556], ['guillemotleft', 500], ['guilsinglleft', 333], ['guilsinglright', 333], ['fi', 500], ['fl', 500], ['endash', 500], ['dagger', 500], ['daggerdbl', 500], ['periodcentered', 250], ['paragraph', 523], ['bullet', 350], ['quotesinglbase', 333], ['quotedblbase', 556], ['quotedblright', 556], ['guillemotright', 500], ['ellipsis', 889], ['perthousand', 1000], ['questiondown', 500], ['grave', 333], ['acute', 333], ['circumflex', 333], ['tilde', 333], ['macron', 333], ['breve', 333], ['dotaccent', 333], ['dieresis', 333], ['ring', 333], ['cedilla', 333], ['hungarumlaut', 333], ['ogonek', 333], ['caron', 333], ['emdash', 889], ['AE', 889], ['ordfeminine', 276], ['Lslash', 556], ['Oslash', 722], ['OE', 944], ['ordmasculine', 310], ['ae', 667], ['dotlessi', 278], ['lslash', 278], ['oslash', 500], ['oe', 667], ['germandbls', 500], ['Idieresis', 333], ['eacute', 444], ['abreve', 500], ['uhungarumlaut', 500], ['ecaron', 444], ['Ydieresis', 556], ['divide', 675], ['Yacute', 556], ['Acircumflex', 611], ['aacute', 500], ['Ucircumflex', 722], ['yacute', 444], ['scommaaccent', 389], ['ecircumflex', 444], ['Uring', 722], ['Udieresis', 722], ['aogonek', 500], ['Uacute', 722], ['uogonek', 500], ['Edieresis', 611], ['Dcroat', 722], ['commaaccent', 250], ['copyright', 760], ['Emacron', 611], ['ccaron', 444], ['aring', 500], ['Ncommaaccent', 667], ['lacute', 278], ['agrave', 500], ['Tcommaaccent', 556], ['Cacute', 667], ['atilde', 500], ['Edotaccent', 611], ['scaron', 389], ['scedilla', 389], ['iacute', 278], ['lozenge', 471], ['Rcaron', 611], ['Gcommaaccent', 722], ['ucircumflex', 500], ['acircumflex', 500], ['Amacron', 611], ['rcaron', 389], ['ccedilla', 444], ['Zdotaccent', 556], ['Thorn', 611], ['Omacron', 722], ['Racute', 611], ['Sacute', 500], ['dcaron', 544], ['Umacron', 722], ['uring', 500], ['threesuperior', 300], ['Ograve', 722], ['Agrave', 611], ['Abreve', 611], ['multiply', 675], ['uacute', 500], ['Tcaron', 556], ['partialdiff', 476], ['ydieresis', 444], ['Nacute', 667], ['icircumflex', 278], ['Ecircumflex', 611], ['adieresis', 500], ['edieresis', 444], ['cacute', 444], ['nacute', 500], ['umacron', 500], ['Ncaron', 667], ['Iacute', 333], ['plusminus', 675], ['brokenbar', 275], ['registered', 760], ['Gbreve', 722], ['Idotaccent', 333], ['summation', 600], ['Egrave', 611], ['racute', 389], ['omacron', 500], ['Zacute', 556], ['Zcaron', 556], ['greaterequal', 549], ['Eth', 722], ['Ccedilla', 667], ['lcommaaccent', 278], ['tcaron', 300], ['eogonek', 444], ['Uogonek', 722], ['Aacute', 611], ['Adieresis', 611], ['egrave', 444], ['zacute', 389], ['iogonek', 278], ['Oacute', 722], ['oacute', 500], ['amacron', 500], ['sacute', 389], ['idieresis', 278], ['Ocircumflex', 722], ['Ugrave', 722], ['Delta', 612], ['thorn', 500], ['twosuperior', 300], ['Odieresis', 722], ['mu', 500], ['igrave', 278], ['ohungarumlaut', 500], ['Eogonek', 611], ['dcroat', 500], ['threequarters', 750], ['Scedilla', 500], ['lcaron', 300], ['Kcommaaccent', 667], ['Lacute', 556], ['trademark', 980], ['edotaccent', 444], ['Igrave', 333], ['Imacron', 333], ['Lcaron', 611], ['onehalf', 750], ['lessequal', 549], ['ocircumflex', 500], ['ntilde', 500], ['Uhungarumlaut', 722], ['Eacute', 611], ['emacron', 444], ['gbreve', 500], ['onequarter', 750], ['Scaron', 500], ['Scommaaccent', 500], ['Ohungarumlaut', 722], ['degree', 400], ['ograve', 500], ['Ccaron', 667], ['ugrave', 500], ['radical', 453], ['Dcaron', 722], ['rcommaaccent', 389], ['Ntilde', 667], ['otilde', 500], ['Rcommaaccent', 611], ['Lcommaaccent', 556], ['Atilde', 611], ['Aogonek', 611], ['Aring', 611], ['Otilde', 722], ['zdotaccent', 389], ['Ecaron', 611], ['Iogonek', 333], ['kcommaaccent', 444], ['minus', 675], ['Icircumflex', 333], ['ncaron', 500], ['tcommaaccent', 278], ['logicalnot', 675], ['odieresis', 500], ['udieresis', 500], ['notequal', 549], ['gcommaaccent', 500], ['eth', 500], ['zcaron', 389], ['ncommaaccent', 500], ['onesuperior', 300], ['imacron', 278], ['Euro', 500],
  ])], ['ZapfDingbats', new Map<string, number>([
    ['space', 278], ['a1', 974], ['a2', 961], ['a202', 974], ['a3', 980], ['a4', 719], ['a5', 789], ['a119', 790], ['a118', 791], ['a117', 690], ['a11', 960], ['a12', 939], ['a13', 549], ['a14', 855], ['a15', 911], ['a16', 933], ['a105', 911], ['a17', 945], ['a18', 974], ['a19', 755], ['a20', 846], ['a21', 762], ['a22', 761], ['a23', 571], ['a24', 677], ['a25', 763], ['a26', 760], ['a27', 759], ['a28', 754], ['a6', 494], ['a7', 552], ['a8', 537], ['a9', 577], ['a10', 692], ['a29', 786], ['a30', 788], ['a31', 788], ['a32', 790], ['a33', 793], ['a34', 794], ['a35', 816], ['a36', 823], ['a37', 789], ['a38', 841], ['a39', 823], ['a40', 833], ['a41', 816], ['a42', 831], ['a43', 923], ['a44', 744], ['a45', 723], ['a46', 749], ['a47', 790], ['a48', 792], ['a49', 695], ['a50', 776], ['a51', 768], ['a52', 792], ['a53', 759], ['a54', 707], ['a55', 708], ['a56', 682], ['a57', 701], ['a58', 826], ['a59', 815], ['a60', 789], ['a61', 789], ['a62', 707], ['a63', 687], ['a64', 696], ['a65', 689], ['a66', 786], ['a67', 787], ['a68', 713], ['a69', 791], ['a70', 785], ['a71', 791], ['a72', 873], ['a73', 761], ['a74', 762], ['a203', 762], ['a75', 759], ['a204', 759], ['a76', 892], ['a77', 892], ['a78', 788], ['a79', 784], ['a81', 438], ['a82', 138], ['a83', 277], ['a84', 415], ['a97', 392], ['a98', 392], ['a99', 668], ['a100', 668], ['a89', 390], ['a90', 390], ['a93', 317], ['a94', 317], ['a91', 276], ['a92', 276], ['a205', 509], ['a85', 509], ['a206', 410], ['a86', 410], ['a87', 234], ['a88', 234], ['a95', 334], ['a96', 334], ['a101', 732], ['a102', 544], ['a103', 544], ['a104', 910], ['a106', 667], ['a107', 760], ['a108', 760], ['a112', 776], ['a111', 595], ['a110', 694], ['a109', 626], ['a120', 788], ['a121', 788], ['a122', 788], ['a123', 788], ['a124', 788], ['a125', 788], ['a126', 788], ['a127', 788], ['a128', 788], ['a129', 788], ['a130', 788], ['a131', 788], ['a132', 788], ['a133', 788], ['a134', 788], ['a135', 788], ['a136', 788], ['a137', 788], ['a138', 788], ['a139', 788], ['a140', 788], ['a141', 788], ['a142', 788], ['a143', 788], ['a144', 788], ['a145', 788], ['a146', 788], ['a147', 788], ['a148', 788], ['a149', 788], ['a150', 788], ['a151', 788], ['a152', 788], ['a153', 788], ['a154', 788], ['a155', 788], ['a156', 788], ['a157', 788], ['a158', 788], ['a159', 788], ['a160', 894], ['a161', 838], ['a163', 1016], ['a164', 458], ['a196', 748], ['a165', 924], ['a192', 748], ['a166', 918], ['a167', 927], ['a168', 928], ['a169', 928], ['a170', 834], ['a171', 873], ['a172', 828], ['a173', 924], ['a162', 924], ['a174', 917], ['a175', 930], ['a176', 931], ['a177', 463], ['a178', 883], ['a179', 836], ['a193', 836], ['a180', 867], ['a199', 867], ['a181', 696], ['a200', 696], ['a182', 874], ['a201', 874], ['a183', 760], ['a184', 946], ['a197', 771], ['a185', 865], ['a194', 771], ['a198', 888], ['a186', 967], ['a195', 888], ['a187', 831], ['a188', 873], ['a189', 927], ['a190', 970], ['a191', 918],
  ])]
])

class EmptyObject {
}

let EOF:EmptyObject= new EmptyObject();


function isEOF(v: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): boolean {
  return v instanceof EmptyObject;
}

class Parser {
  private lexer: Lexer;
  private allowStreams: boolean;
  private xref: XRef;
  private inlineImg: number;

  private buf1: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
  private buf2: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;

  constructor(lexer: Lexer, allowStreams: boolean, xref: XRef) {
    this.lexer = lexer;
    this.allowStreams = allowStreams;
    this.xref = xref;
    this.inlineImg = 0;
    this.refill();
  }

  private refill(): void {
    this.buf1 = this.lexer.getObj();
    this.buf2 = this.lexer.getObj();
  }

  private shift(): void {
    if (isCmd(this.buf2, 'ID')) {
      this.buf1 = this.buf2;
      this.buf2 = null;
      this.lexer.skip();
    } else {
      this.buf1 = this.buf2;
      this.buf2 = this.lexer.getObj();
    }
  }

  getObj(cipherTransform: CipherTransform = null): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    if (isCmd(this.buf1, 'BI')) {
      this.shift();
      return this.makeInlineImage(cipherTransform);
    }
    if (isCmd(this.buf1, '[')) {
      this.shift();
      let array:(object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = new Array<object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>();
      while (!isCmd(this.buf1, ']') && !isEOF(this.buf1)) {
        array.push(this.getObj());
      }
      if (isEOF(this.buf1)) {
        error('End of file inside array');
      }
      this.shift();
      return array;
    }
    if (isCmd(this.buf1, '<<')) {
      this.shift();
      let dict:Dict = new Dict(this.xref);
      while (!isCmd(this.buf1, '>>') && !isEOF(this.buf1)) {
        if (!isName(this.buf1)) {
          error('Dictionary key must be a name object');
        }
        let key:string = (this.buf1 as Name).name;
        this.shift();
        if (isEOF(this.buf1)) {
          break;
        }
        dict.set(key, this.getObj(cipherTransform));
      }
      if (isEOF(this.buf1)) {
        error('End of file inside dictionary');
      }


      if (isCmd(this.buf2, 'stream')) {
        return this.allowStreams ? this.makeStream(dict, cipherTransform) : dict;
      }
      this.shift();
      return dict;
    }
    if (isInt(this.buf1)) {
      let num: number = this.buf1 as number;
      this.shift();
      if (isInt(this.buf1) && isCmd(this.buf2, 'R')) {
        let ref:Ref = new Ref(num, this.buf1 as number);
        this.shift();
        this.shift();
        return ref;
      }
      return num;
    }
    if (isString(this.buf1)) {
      let str: string = this.buf1 as string;
      this.shift();
      if (cipherTransform !== null && cipherTransform !== undefined){
        return cipherTransform.decryptString(str);
      }
      return str;
    }


    let obj = this.buf1;
    this.shift();
    return obj;
  }

  makeInlineImage(cipherTransform?: CipherTransform): Stream | null {
    let lexer:Lexer = this.lexer;
    let stream:Stream = lexer.stream;


    let dict:Dict = new Dict();
    while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) {
      if (!isName(this.buf1)) {
        error('Dictionary key must be a name object');
      }
      let nameTmep: Name = this.buf1 as Name;
      let key:string = nameTmep.name;
      this.shift();
      if (isEOF(this.buf1)) {
        break;
      }
      dict.set(key, this.getObj(cipherTransform) as string);
    }


    let startPos:number = stream.pos;


    let state:number = 0;
    let ch:number = stream.getByte();
    while (state !== Number_4 && ch !== null) {
      switch (ch) {
        case 0x20:
        case 0x0D:
        case 0x0A:
          state = state === Number_3 ? Number_4 : 0;
          break;
        case 0x45:
          state = Number_2;
          break;
        case 0x49:
          state = state === Number_2 ? Number_3 : 0;
          break;
        default:
          state = 0;
          break;
      }
      ch = stream.getByte();
    }


    let inlineImgLimit:number = 500;
    if (++this.inlineImg >= inlineImgLimit) {
      if (this.inlineImg === inlineImgLimit) {
        warn('Too many inline images');
      }
      this.shift();
      return null;
    }

    let length:number = (stream.pos - Number_4) - startPos;
    let imageStream: Stream = stream.makeSubStream(startPos, length, dict);
    if (cipherTransform !== null && cipherTransform !== undefined){
      imageStream = cipherTransform.createStream(imageStream);
    }
    imageStream = this.filter(imageStream, dict, length);
    imageStream.parameters = dict;

    this.buf2 = Cmd.get('EI');
    this.shift();

    return imageStream;
  }

  fetchIfRef(obj: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {

    return isRef(obj) ? this.xref.fetch(obj as Ref) : obj;
  }

  makeStream(dict: Dict, cipherTransform: CipherTransform = null): Stream {
    let lexer:Lexer = this.lexer;
    let stream:Stream = lexer.stream;


    lexer.skipToNextLine();
    let pos:number = stream.pos;


    let length:number = this.fetchIfRef(dict.get('Length')) as number;
    if (!isInt(length)) {
      error(`Bad ${length} attribute in stream`);
    }


    stream.pos = pos + length;
    this.shift();
    this.shift();
    if (!isCmd(this.buf1, 'endstream')) {
      error('Missing endstream');
    }
    this.shift();

    let filteredStream:Stream = stream.makeSubStream(pos, length, dict);
    if (cipherTransform) {
      filteredStream = cipherTransform.createStream(filteredStream);
    }
    filteredStream = this.filter(filteredStream, dict, length);
    filteredStream.parameters = dict;
    return filteredStream;
  }

  filter(stream: Stream, dict: Dict, length: number): Stream {
    let filter:object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = this.fetchIfRef(dict.get('Filter', 'F'));
    let params:object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = this.fetchIfRef(dict.get('DecodeParms', 'DP'));

    let streamResult: Stream = stream;
    length = length;

    if (isName(filter)) {
      return this.makeFilter(stream, (filter as Name).name, length, params as Dict);
    }

    if (isArray(filter)) {
      let filterArray: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = filter as (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[];
      let paramsArray: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = params;

      for (let i: number = 0; i < filterArray.length; ++i) {
        filter = filterArray[i];
        if (!isName(filter)) {
          error(`Bad filter name: ${filter}`);
        }
        params = null;
        if (isArray(paramsArray) && (i < (paramsArray as (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[]).length)) {
          params = paramsArray[i];
        }

        streamResult = this.makeFilter(stream, (filter as Name).name, length, params as Dict);

        length = null;
      }
    }

    return streamResult;
  }

  makeFilter(stream: Stream, name: string, length: number, params: Dict): Stream {
    if (name === 'FlateDecode' || name === 'Fl') {
      if (params !== null && params !== undefined){
        return new PredictorStream(new FlateStream(stream), params);
      }
      return new FlateStream(stream);
    }
    if (name === 'LZWDecode' || name === 'LZW') {
      let earlyChange: number = 1;
      if (params !== null && params !== undefined){
        if (params.has('EarlyChange')) {
          earlyChange = params.get('EarlyChange') as number;
        }
        return new PredictorStream(new LZWStream(stream as StringStream, earlyChange !== 0 ), params);
      }
      return new LZWStream(stream as StringStream, earlyChange !== 0);
    }
    if (name === 'DCTDecode' || name === 'DCT') {
      let bytes:Uint8Array = stream.getBytes(length);
      return new JpegStream(bytes, stream.dict, this.xref);
    }
    if (name === 'JPXDecode' || name === 'JPX') {
      let bytes:Uint8Array = stream.getBytes(length);
      return new JpxStream(bytes, stream.dict);
    }
    if (name === 'ASCII85Decode' || name === 'A85') {
      return new Ascii85Stream(stream as StringStream);
    }
    if (name === 'ASCIIHexDecode' || name === 'AHx') {
      return new AsciiHexStream(stream as StringStream);
    }
    if (name === 'CCITTFaxDecode' || name === 'CCF') {
      return new CCITTFaxStream(stream as StringStream, params);
    }
    if (name === 'RunLengthDecode' || name === 'RL') {
      return new RunLengthStream(stream as StringStream);
    }
    if (name === 'JBIG2Decode') {
      error('JBIG2 image format is not currently supported.');
    }
    warn(`filter "${name}" not supported yet`);

    return stream;
  }

}

class Lexer {
  stream: Stream;

  constructor(stream: Stream) {
    this.stream = stream;
  }

  static isSpace(ch: string): boolean {
    return ch == ' ' || ch == '\t' || ch == '\x0d' || ch == '\x0a';
  }

  specialChars: number[] = [
    1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  ];

  toHexDigit(ch: string): number {
    if (ch >= '0' && ch <= '9') {

      return ch.charCodeAt(0) - Number_48;
    }
    let uppercaseCh: string = ch.toUpperCase();

    if (uppercaseCh >= 'A' && uppercaseCh <= 'F') {

      return uppercaseCh.charCodeAt(0) - Number_55;
    }

    return -1;
  }

  getNumber(ch: string): number {
    let floating:boolean = false;
    let str:string = ch;
    let stream:Stream = this.stream;
    while (true) {
      let nextChar:string = stream.lookChar();
      if (nextChar == '.' && !floating) {
        str += nextChar;
        floating = true;
      } else if (nextChar == '-') {


        warn('Badly formatted number');
      } else if (nextChar >= '0' && nextChar <= '9') {
        str += nextChar;
      } else if (nextChar == 'e' || nextChar == 'E') {
        floating = true;
      } else {

        break;
      }

      stream.skip();
    }
    let value:number = Number.parseFloat(str);
    if (Number.isNaN(value)) {
      error(`Invalid floating point number: ${value}`);
    }
    return value;
  }

  getString(): string {
    let numParen:number = 1;
    let done:boolean = false;
    let str:string = '';
    let stream:Stream = this.stream;
    let ch:string;
    while (!done) {
      ch = stream.getChar();
      switch (ch) {
        case undefined:
          print('Unterminated string');
          done = true;
          break;
        case '(':
          ++numParen;
          str += ch;
          break;
        case ')':
          if (--numParen === 0) {
            done = true;
          } else {
            str += ch;
          }
          break;
        case '\\':

          ch = stream.getChar();
          switch (ch) {
            case undefined:
              print('Unterminated string');
              done = true;
              break;
            case 'n':
              str += '\n';
              break;
            case 'r':
              str += '\r';
              break;
            case 't':
              str += '\t';
              break;
            case 'b':
              str += '\b';
              break;
            case 'f':
              str += '\f';
              break;
            case '\\':
            case '(':
            case ')':
              str += ch;
              break;
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
              let x = Number(ch);
              ch = stream.lookChar();

              if (ch >= '0' && ch <= '7') {
                stream.skip();
                x = (x << Number_3) + Number(ch);
                ch = stream.lookChar();
                if (ch >= '0' && ch <= '7') {
                  stream.skip();
                  x = (x << Number_3) + Number(ch);
                }
              }

              str += String.fromCharCode(x);
              break;
            case '\r':
              ch = stream.lookChar();

              if (ch === '\n') {
                stream.skip();
              }
              break;
            case '\n':
              break;
            default:
              str += ch;
          }
          break;
        default:
          str += ch;
      }
    }

    return str;
  }

  getName(ch:string): Name {
    let str:string = '';
    let stream:Stream = this.stream;
    let tempCh:string = stream.lookChar();
    while (!!tempCh && !this.specialChars[tempCh.charCodeAt(0)]) {
      stream.skip();
      if (tempCh === '#') {
        tempCh = stream.lookChar();
        let x:number = this.toHexDigit(tempCh);

        if (x !== -1) {
          stream.skip();
          let x2:number = this.toHexDigit(stream.getChar());

          if (x2 === -1) {
            error(`Illegal digit in hex char in name: ${x2}`);
          }

          str += String.fromCharCode((x << Number_4) | x2);

        } else {
          str += '#';
          str += tempCh;
        }
      } else {
        str += tempCh;
      }
      tempCh = stream.lookChar();
    }

    if (str.length > Number_128) {
      error(
        'Warning: name token is longer than allowed by the spec: ' +
        str.length
      );
    }

    return new Name(str);
  }

  getHexString(ch:string): string {
    let str:string = '';
    let stream:Stream = this.stream;
    while (true) {
      let tempCh:string = stream.getChar();

      if (tempCh === '>') {
        break;
      }
      if (tempCh === null || tempCh === undefined){
        print('Unterminated hex string');
        break;
      }

      if (this.specialChars[tempCh.charCodeAt(0)] !== 1) {
        let x:number = this.toHexDigit(tempCh);

        if (x === -1) {
          error(`Illegal character in hex string: ${tempCh}`);
        }

        tempCh = stream.getChar();

        while (this.specialChars[ch.charCodeAt(0)] === 1) {
          ch = stream.getChar();
        }
        let x2:number = this.toHexDigit(ch);
        if (x2  === -1) {
          error(`Illegal character in hex string: ${ch}`);
        }

        str += String.fromCharCode((x << Number_4) | x2);

      }
    }

    return str;
  }

  getObj(): Name | Cmd | EmptyObject{

    let comment:boolean = false;
    let stream:Stream = this.stream;
    let ch: string = "";
    while (true) {
      ch = stream.getChar()
      if (ch === null || ch === undefined){
        return EOF;
      }

      if (comment) {
        if (ch === '\r' || ch === '\n') {
          comment = false;
        }
      } else if (ch === '%') {
        comment = true;
      } else if (this.specialChars[ch.charCodeAt(0)] !== 1) {
        break;
      }

    }

    switch (ch) {
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
      case '+':
      case '-':
      case '.':
        return this.getNumber(ch);
      case '(':
        return this.getString();
      case '/':
        return this.getName(ch);

      case '[':
      case ']':
        return Cmd.get(ch);

      case '<':
        ch = stream.lookChar();
        if (ch == '<') {

          stream.skip();
          return Cmd.get('<<');
        }
        return this.getHexString(ch);

      case '>':
        ch = stream.lookChar();
        if (ch == '>') {
          stream.skip();
          return Cmd.get('>>');
        }
      case '{':
      case '}':
        return Cmd.get(ch);
      case ')':
        error(`Illegal character: ${ch}`);
      default:
        break;
    }
    let str:string = ch;
    let tempCh:string = stream.lookChar();
    while (!!tempCh && this.specialChars[tempCh.charCodeAt(0)] === 0) {
      stream.skip();
      if (str.length === Number_128) {
        error(`Command token too long: ${str.length}`);
      }
      str += tempCh;
      tempCh = stream.lookChar();
    }

    if (str === 'true') {
      return true;
    }
    if (str === 'false') {
      return false;
    }
    if (str === 'null') {
      return null;
    }
    return Cmd.get(str);
  }

  skipToNextLine(): void {
    const stream = this.stream;
    while (true) {
      let ch: string = stream.getChar();
      if (!ch || ch == '\n') {
        return;
      }
      if (ch == '\r') {
        ch = stream.lookChar();
        if (ch == '\n') {
          stream.skip();
        }
        return;
      }
    }
  }

  skip(): void {
    this.stream.skip();
  }
}

class PredictorStream extends DecodeStream {
  private predictor: number = 0;
  private colors: number = 0;
  private bits: number = 0;
  private columns: number = 0;
  private pixBytes: number = 0;
  private rowBytes: number = 0;
  private stream?: Stream = null;
  readBlock: () => void = null;

  constructor(stream: Stream, params: Dict) {
    super();
    this.predictor = params.get('Predictor') as number || 1;
    if (this.predictor <= 1) {
      return;
    }
    if (this.predictor !== Number_2 && (this.predictor < Number_10 || this.predictor > Number_15)) {
      error(`Unsupported predictor: + ${this.predictor}`);
    }
    if (this.predictor === Number_2) {
      this.readBlock = this.readBlockTiff;
    } else {
      this.readBlock = this.readBlockPng;
    }
    this.stream = stream;
    this.dict = stream.dict;
    this.colors = params.get('Colors') as number || 1;
    this.bits = params.get('BitsPerComponent') as number || Number_8;
    this.columns = params.get('Columns') as number || 1;
    this.pixBytes = (this.colors * this.bits + Number_7) >> Number_3;
    this.rowBytes = (this.columns * this.colors * this.bits + Number_7) >> Number_3;
  }

  private readBlockTiff(): void {
    const rowBytes: number = this.rowBytes;
    const bufferLength: number = super.bufferLength;
    let buffer: Uint8Array = this.ensureBuffer(bufferLength + rowBytes);
    const bits: number = this.bits;
    const colors: number = this.colors;
    const rawBytes: Uint8Array = this.stream.getBytes(rowBytes);
    let inbuf: number = 0;
    let outbuf: number = 0;
    let inbits: number = 0;
    let outbits: number = 0;
    let pos: number = bufferLength;
    if (bits === 1) {
      for (let i = 0; i < rowBytes; ++i) {
        const c: number = rawBytes[i];
        inbuf = (inbuf << Number_8) | c;
        buffer[pos] = (c ^ (inbuf >> colors)) & Number_0xFF;
        inbuf &= Number_0xFFFF;
        pos += 1;
      }
    } else if (bits === Number_8) {
      for (let i = 0; i < colors; ++i) {
        buffer[pos] = rawBytes[i];
        pos += 1;
      }
      for (let i = colors; i < rowBytes; ++i) {
        buffer[pos] = buffer[pos - colors] + rawBytes[i];
        pos += 1;
      }
    } else {
      let compArray: Uint8Array = new Uint8Array(colors + 1);
      const bitMask: number = (1 << bits) - 1;
      let j: number = 0;
      let k: number = bufferLength;
      const columns: number = this.columns;
      for (let i = 0; i < columns; ++i) {
        for (let kk = 0; kk < colors; ++kk) {
          if (inbits < bits) {
            inbuf = (inbuf << Number_8) | (rawBytes[j] & Number_0xFF);
            inbits += Number_8;
            j += 1;
          }
          const temp:number = (compArray[kk] + (inbuf >> (inbits - bits))) & bitMask;
          compArray[kk] = temp
          inbits -= bits;
          outbuf = (outbuf << bits) | compArray[kk];
          outbits += bits;
          if (outbits >= Number_8) {
            buffer[k] = (outbuf >> (outbits - Number_8)) & Number_0xFF;
            outbits -= Number_8;
            k += 1;
          }
        }
      }
      if (outbits > 0) {
        const temp1:number = outbuf << (Number_8 - outbits);
        const temp2:number = inbuf & ((1 << (Number_8 - outbits)) - 1);
        buffer[k] = temp1 + temp2;
        k += 1;
      }
    }
    this.bufferLength += rowBytes;
  }

  private readBlockPng(): void {
    const rowBytes: number = this.rowBytes;
    const pixBytes: number = this.pixBytes;
    const predictor: number = this.stream.getByte();
    const rawBytes: Uint8Array = this.stream.getBytes(rowBytes);

    const bufferLength: number = this.bufferLength;
    let buffer: Uint8Array = this.ensureBuffer(bufferLength + rowBytes);
    const startArray: number = bufferLength - rowBytes;
    const endArray: number = startArray + bufferLength;
    let prevRow: Uint8Array = buffer.subarray(startArray, endArray);
    if (prevRow.length === 0) {
      prevRow = new Uint8Array(rowBytes);
    }
    let j: number = bufferLength;
    switch (predictor) {
      case 0:
        for (let i = 0; i < rowBytes; ++i) {
          buffer[j] = rawBytes[i];
          j += 1;
        }
        break;
      case 1:
        for (let i = 0; i < pixBytes; ++i) {
          buffer[j] = rawBytes[i];
          j += 1;
        }
        for (let i = pixBytes; i < rowBytes; ++i) {
          buffer[j] = (buffer[j - pixBytes] + rawBytes[i]) & Number_0xFF;
          j += 1;
        }
        break;
      case Number_2:
        for (let i = 0; i < rowBytes; ++i) {
          buffer[j] = (prevRow[i] + rawBytes[i]) & Number_0xFF;
          j += 1;
        }
        break;
      case Number_3:
        for (let i = 0; i < pixBytes; ++i) {
          buffer[j] = (prevRow[i] >> 1) + rawBytes[i];
          j += 1;
        }
        for (let i = pixBytes; i < rowBytes; ++i) {
          buffer[j] = (((prevRow[i] + buffer[j - pixBytes]) >> 1) + rawBytes[i]) & Number_0xFF;
          j += 1;
        }
        break;
      case Number_4:
        for (let i = 0; i < pixBytes; ++i) {
          const up: number = prevRow[i];
          const c: number = rawBytes[i];
          buffer[j] = up + c;
          j += 1;
        }
        for (let i = pixBytes; i < rowBytes; ++i) {
          const up: number = prevRow[i];
          const upLeft: number = prevRow[i - pixBytes];
          const left: number = buffer[j - pixBytes];
          const p: number = left + up - upLeft;
          let pa: number = p - left;
          if (pa < 0) {
            pa = -pa;
          }
          let pb: number = p - up;
          if (pb < 0) {
            pb = -pb;
          }
          let pc: number = p - upLeft;
          if (pc < 0) {
            pc = -pc;
          }

          const c: number = rawBytes[i];
          if (pa <= pb && pa <= pc) {
            buffer[j] = left + c;
          }
          else if (pb <= pc) {
            buffer[j] = up + c;
          } else {
            buffer[j] = upLeft + c;
          }
          j += 1;
        }
        break;
      default:
        error('Unsupported predictor: ' + "${predictor}");
    }
    this.bufferLength += rowBytes;
  }
}

class Linearization {
  private parser: Parser;
  private linDict?: Dict;
  constructor(stream: Stream) {
    this.parser = new Parser(new Lexer(stream), false, null);
    const obj1: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = this.parser.getObj();
    const obj2: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name  = this.parser.getObj();
    const obj3: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name  = this.parser.getObj();
    this.linDict = this.parser.getObj() as Dict;

    if (isInt(obj1) && isInt(obj2) && isCmd(obj3, 'obj') && isDict(this.linDict)) {
      let obj: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = this.linDict?.get("Linearized");
      if (!(isNum(obj) && obj as number > 0)) {
        this.linDict = null;
      }
    }
  }

  getInt(name: string): number {
    const linDict: Dict = this.linDict;
    const obj: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = linDict?.get[name];
    if (isDict(linDict) && isInt(obj) && obj as number > 0) {
      return obj as number;
    }
    error(`${name} field in linearization table is invalid`);
    return 0;
  }

  getHint(index: number): number {
    const linDict: Dict | undefined = this.linDict;
    let obj1: number[] | undefined;
    let obj2: number | undefined;
    if (isDict(linDict) && isArray(obj1 = linDict.get("H") as number[]) && obj1.length >= Number_2 && isInt(obj2 = obj1[index]) && obj2 > 0) {
      return obj2!;
    }
    error(`Hints table in linearization table is invalid: ${index}`);
    return obj2!;
  }


  get length(): number {
    if (!isDict(this.linDict)) {
      return 0;
    }
    return this.getInt('L');
  }

  get hintsOffset(): number {
    return this.getHint(0);
  }

  get hintsLength(): number {
    return this.getHint(1);
  }

  get hintsOffset2(): number {
    return this.getHint(Number_2);
  }

  get hintsLength2(): number {
    return this.getHint(Number_3);
  }

  get objectNumberFirst(): number {
    return this.getInt('O');
  }

  get endFirst(): number {
    return this.getInt('E');
  }

  get numPages(): number {
    return this.getInt('N');
  }

  get mainXRefEntriesOffset(): number {
    return this.getInt('T');
  }

  get pageFirst(): number {
    return this.getInt('P');
  }
}

enum PatternType {
  AXIAL = 2,
  RADIAL = 3
}
enum PaintType {
  COLORED = 1,
  UNCOLORED = 2
}
const MAX_PATTERN_SIZE:number = 512.0;
class TilingPattern {
  private matrix: number[];
  private curMatrix: number[];
  private invMatrix: number[];
  private ctx: PDFContext;
  private type: string;
  private canvas: Canvas;
  private scale: number[];

  constructor(IR: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[], color: number[], ctx: PDFContext, objs: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name) {
    const operatorList:OperatorList = IR[Number_2] as OperatorList;
    this.matrix = IR[Number_3] as number[];
    const bbox:number[] = IR[Number_4] as number[];
    const xstep:number = IR[Number_5] as number;
    const ystep:number = IR[Number_6] as number;
    const paintType:PaintType = IR[Number_7] as PaintType;
    TODO("TilingType")
    this.curMatrix = ctx.mozCurrentTransform;
    this.invMatrix = ctx.mozCurrentTransformInverse;
    this.ctx = ctx;
    this.type = 'Pattern';
    const x0:number = bbox[0];
    const y0:number = bbox[1];
    const x1:number = bbox[Number_2];
    const y1:number = bbox[Number_3];
    const topLeft:number[] = [x0, y0];
    const botRight:number[] = [x0 + xstep, y0 + ystep];
    let width:number = botRight[0] - topLeft[0];
    let height:number = botRight[1] - topLeft[1];
    while (Math.abs(width) > MAX_PATTERN_SIZE || Math.abs(height) > MAX_PATTERN_SIZE) {
      width = MAX_PATTERN_SIZE;
      height = MAX_PATTERN_SIZE;
    }
    const tmpCanvas:Canvas = createScratchCanvas(width, height);
    const tmpCtx: PDFContext = tmpCanvas.getContext('2d');
    const graphics:CanvasGraphics = new CanvasGraphics(tmpCtx, objs as PDFObjects);

    switch (paintType) {
      case PaintType.COLORED:
        tmpCtx.fillStyle = ctx.fillStyle;
        tmpCtx.strokeStyle = ctx.strokeStyle;
        break;
      case PaintType.UNCOLORED:
        const cssColor:string = Util.makeCssRgb(color[0], color[1], color[Number_2]);
        tmpCtx.fillStyle = cssColor;
        tmpCtx.strokeStyle = cssColor;
        break;
      default:
        error(`Unsupported paint type: ${paintType}`);
    }
    const scale:number[] = [width / xstep, height / ystep];
    this.scale = scale;

    const tmpTranslate:number[] = [1, 0, 0, 1, -topLeft[0], -topLeft[1]];
    const tmpScale:number[] = [scale[0], 0, 0, scale[1], 0, 0];

    graphics.transform(tmpTranslate[0], tmpTranslate[1], tmpTranslate[Number_2], tmpTranslate[Number_3], tmpTranslate[Number_4], tmpTranslate[Number_5])
    graphics.transform(tmpScale[0], tmpScale[1], tmpScale[Number_2], tmpScale[Number_3], tmpScale[Number_4], tmpScale[Number_5])

    if (bbox !== null && isArray(bbox) && bbox.length === Number_4) {
      const bboxWidth:number = x1 - x0;
      const bboxHeight:number = y1 - y0;
      graphics.rectangle(x0, y0, bboxWidth, bboxHeight);
      graphics.clip();
      graphics.endPath();
    }
    graphics.executeOperatorList(operatorList);
    this.canvas = tmpCanvas;
  }

  static getIR(operatorList: OperatorList, dict: Dict, args: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] {
    const matrix: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = dict.get('Matrix') as object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
    const bbox: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = dict.get('BBox') as object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
    const xstep: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = dict.get('XStep') as object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
    const ystep: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = dict.get('YStep') as object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
    const paintType: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = dict.get('PaintType') as object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
    return ['TilingPattern', args, operatorList, matrix, bbox, xstep, ystep, paintType];
  }

  getPattern(): Pattern {
    const matrix: number[] = this.matrix;
    const curMatrix: number[] = this.curMatrix;
    const ctx: PDFContext = this.ctx;
    if (curMatrix !== null) {
      ctx.transform(curMatrix[0], curMatrix[1],curMatrix[Number_2],curMatrix[Number_3],curMatrix[Number_4],curMatrix[Number_5]);
    }
    if (matrix !== null) {
      ctx.transform(matrix![0], matrix![1], matrix![Number_2], matrix![Number_3], matrix![Number_4], matrix![Number_5])
    }
    const scale:number[] = this.scale;
    ctx.scale(1 / scale[0], 1 / scale[1]);

    return ctx.createPattern(this.canvas, 'repeat');
  }
}

class Pattern {
  constructor() {
    error('should not call Pattern constructor');
  }
  getPattern(ctx: PDFContext): Pattern | string {
    error(`Should not call Pattern.getStyle: ${ctx}`);
    return new Pattern();
  }
  static shadingFromIR(raw: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[]):  Pattern | TilingPattern {
    return Shadings.RadialAxial.fromIR(raw) as Pattern;
  }

  static parseShading(shading: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name, matrix: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name, xref: XRef, res: Dict): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
    const dict: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = isStream(shading) ? (shading as Stream).dict : shading;
    const type:number = (dict as Dict).get('ShadingType') as number;
    switch (type) {
      case PatternType.AXIAL:
      case PatternType.RADIAL:
        return new Shadings.RadialAxial(dict as Dict, matrix, xref, res);
      default:
        return new Shadings.Dummy();
    }
  }
}
namespace Shadings {
   class RadialAxial {
     private matrix: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
     private coordsArr: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
     private shadingType: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
     private type: string;
     private ctx: PDFContext;
     private cs: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
     private extendStart: boolean;
     private extendEnd: boolean;
     private colorStops: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[][];

     constructor(dict: Dict, matrix: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name, xref: XRef, res: Dict, ctx: PDFContext = null) {
       this.matrix = matrix;
       this.coordsArr = dict.get('Coords');
       this.shadingType = dict.get('ShadingType');
       this.type = 'Pattern';

       this.ctx = ctx;
       let cs: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = dict.get('ColorSpace', 'CS');
       cs = ColorSpace.parse(cs, xref, res);
       this.cs = cs;

       let t0:number = 0.0;
       let t1:number = 1.0;
       if (dict.has('Domain')) {
         const domainArr:number[] = dict.get('Domain') as number[];
         t0 = domainArr[0];
         t1 = domainArr[1];
       }

       let extendStart:boolean = false;
       let extendEnd:boolean = false;
       if (dict.has('Extend')) {
         const extendArr:boolean = dict.get('Extend') as boolean;
         extendStart = extendArr[0];
         extendEnd = extendArr[1];
         ('Support extend')
       }
       this.extendStart = extendStart;
       this.extendEnd = extendEnd;

       let fnObj: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = dict.get('Function');
       if (isArray(fnObj)) {
         error('No support for array of functions');
       }
       if (!isPDFFunction(fnObj)) {
         error('Invalid function');
       }

       const fn:(args:number[])=>number[] = PDFFunction.parse(xref, fnObj) as (args:number[])=>number[];

       const step:number = (t1 - t0) / Number_10;
       const diff:number = t1 - t0;

       let colorStops: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[][] = new Array<(object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[]>();
       let i:number = t0
       while (i <= t1) {
         const rgbColor:number[] = (cs as DeviceRgbCS).getRgb(fn([i]) as number[]);
         const cssColor:string = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[Number_2]);
         colorStops.push([(i - t0) / diff, cssColor]);
         i += step;
       }
       this.colorStops = colorStops;
     }

     static fromIR(raw: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[]): object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name {
       const type:number = raw[1] as number;
       const colorStops: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[][] = raw[Number_2] as (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[][];
       let p0:number[] = raw[Number_3] as number[];
       let p1:number[] = raw[Number_4] as number[];
       const r0:number = raw[Number_5] as number;
       const r1:number = raw[Number_6] as number;
       return {
         type: 'Pattern',
         getPattern: (ctx: PDFContext):Gradient => {
           const curMatrix:number[] = ctx.mozCurrentTransform;
           if (curMatrix.length > 0) {
             let userMatrix:number[] = ctx.mozCurrentTransformInverse;
             p0 = Util.applyTransform(p0, curMatrix)
             p0 = Util.applyTransform(p0, userMatrix)
             p1 = Util.applyTransform(p1, curMatrix)
             p1 = Util.applyTransform(p1, userMatrix)
           }
           let grad:Gradient = new Gradient();
           if (type == PatternType.AXIAL){
             grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]);
           }else if (type == PatternType.RADIAL){
             grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1);
           }
           for (let i = 0;  i < colorStops.length; ++i) {
             const c: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = colorStops[i]
             grad.addColorStop(c[0] as number, c[1] as string);
           }
           return grad;
         }
       };
     }

     getIR(): (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] {
       const coordsArr:number[] = this.coordsArr as number[];
       const type:PatternType = this.shadingType as PatternType;
       let p0: number[] = new Array<number>();
       let p1: number[] = new Array<number>();
       let r0: number;
       let r1: number;
       if (type == PatternType.AXIAL) {
         p0 = [coordsArr[0], coordsArr[1]];
         p1 = [coordsArr[Number_2], coordsArr[Number_3]];
       } else if (type == PatternType.RADIAL) {
         p0 = [coordsArr[0], coordsArr[1]];
         p1 = [coordsArr[Number_3], coordsArr[Number_4]];
         r0 = coordsArr[Number_2];
         r1 = coordsArr[Number_5];
       } else {
         error(`getPattern type unknown: ${type}`);
       }

       const matrix:number[] = this.matrix as number[];
       if (matrix.length !== 0) {
         p0 = Util.applyTransform(p0, matrix);
         p1 = Util.applyTransform(p1, matrix);
       }
       return ['RadialAxial', type, this.colorStops, p0, p1, r0, r1];
     }
   }

   class Dummy {
     private type: string;

     constructor() {
       this.type = 'Pattern';
     }

     static fromIR(): string {
       return 'hotpink';
     }

     getIR(): (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] {
       return ['Dummy'];
     }
   }
}

class StringStream extends Stream {
  constructor(str: string) {
    const length = str.length;
    let bytes = new Uint8Array(length);
    for (let n = 0; n < length; ++n) {
      bytes[n] = str.charCodeAt(n);
    }
    super(bytes);
  }
}

class FakeStream extends DecodeStream {
  constructor(stream: Stream) {
    super();
    this.dict = stream.dict;
  }
  readBlock(): void {
    let bufferLength:number = this.bufferLength;
    bufferLength += Number_1024;
    const buffer:Uint8Array = this.ensureBuffer(bufferLength);
    this.bufferLength = bufferLength;
  }

  getBytes(length: number): Uint8Array | null {
    let end: number;
    let pos: number = this.pos;

    if (length !== 0) {
      this.ensureBuffer(pos + length);
      end = pos + length;

      while (!this.eof && this.bufferLength < end){
        this.readBlock();
      }
      const bufEnd:number = this.bufferLength;
      if (end > bufEnd){
        end = bufEnd;
      }
    } else {
      this.eof = true;
      end = this.bufferLength;
    }

    this.pos = end;
    return this.buffer!.subarray(pos, end);
  }
}

class StreamsSequenceStream extends DecodeStream {
  private streams: Stream[] = null;
  constructor(streams: Stream[]) {
    super();
    this.streams = streams;
  }
  readBlock(): void {
    const streams:Stream[] = this.streams;
    if (streams.length === 0) {
      this.eof = true;
      return;
    }
    const stream: Stream = streams.shift();
    const chunk: Uint8Array = stream.getBytes();
    const bufferLength: number = this.bufferLength;
    const newLength: number = bufferLength + chunk.length;
    const buffer: Uint8Array | null = this.ensureBuffer(newLength);
    buffer.set(chunk, bufferLength);
    this.bufferLength = newLength;
  }
}
class FlateStream extends DecodeStream {
  private bytesPos: number = 0;
  private codeSize: number = 0;
  private codeBuf: number = 0;
  private static codeLenCodeMap = new Uint32Array([
    16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
  ]);
  private lengthDecode: Uint32Array = new Uint32Array([
    0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a, 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f, 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, 0x40053, 0x40063, 0x40073, 0x50083, 0x500a3, 0x500c3, 0x500e3, 0x00102, 0x00102, 0x00102
  ]);
  private distDecode: Uint32Array = new Uint32Array([
    0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d, 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1, 0x70101, 0x70181, 0x80201, 0x80301, 0x90401, 0x90601, 0xa0801, 0xa0c01, 0xb1001, 0xb1801, 0xc2001, 0xc3001, 0xd4001, 0xd6001
  ]);
  private static fixedLitCodeTab: [Uint32Array, number] = [new Uint32Array([
    0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0, 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0, 0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0, 0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0, 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8, 0x7010a, 0x80064, 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8, 0x70106, 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8, 0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, 0x900f8, 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c4, 0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, 0x80082, 0x80042, 0x900e4, 0x70105, 0x8005a, 0x8001a, 0x90094, 0x70115, 0x8007a, 0x8003a, 0x900d4, 0x7010d, 0x8006a, 0x8002a, 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4, 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc, 0x7010b, 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec, 0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, 0x900dc, 0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, 0x8004e, 0x900fc, 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c2, 0x70108, 0x80061, 0x80021, 0x900a2, 0x80001, 0x80081, 0x80041, 0x900e2, 0x70104, 0x80059, 0x80019, 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2, 0x7010c, 0x80069, 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2, 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca, 0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, 0x900ea, 0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, 0x8003d, 0x900da, 0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, 0x8008d, 0x8004d, 0x900fa, 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c6, 0x70109, 0x80063, 0x80023, 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6, 0x70105, 0x8005b, 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6, 0x7010d, 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6, 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900ce, 0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, 0x80047, 0x900ee, 0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, 0x8007f, 0x8003f, 0x900de, 0x7010f, 0x8006f, 0x8002f, 0x900be, 0x8000f, 0x8008f, 0x8004f, 0x900fe, 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1, 0x70108, 0x80060, 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1, 0x70104, 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1, 0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, 0x900f1, 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c9, 0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, 0x80084, 0x80044, 0x900e9, 0x70106, 0x8005c, 0x8001c, 0x90099, 0x70116, 0x8007c, 0x8003c, 0x900d9, 0x7010e, 0x8006c, 0x8002c, 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9, 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5, 0x70109, 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5, 0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, 0x900d5, 0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, 0x8004a, 0x900f5, 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cd, 0x7010b, 0x80066, 0x80026, 0x900ad, 0x80006, 0x80086, 0x80046, 0x900ed, 0x70107, 0x8005e, 0x8001e, 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd, 0x7010f, 0x8006e, 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd, 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3, 0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, 0x900e3, 0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, 0x80039, 0x900d3, 0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, 0x80089, 0x80049, 0x900f3, 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900cb, 0x7010a, 0x80065, 0x80025, 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb, 0x70106, 0x8005d, 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db, 0x7010e, 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb, 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c7, 0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, 0x80043, 0x900e7, 0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, 0x8007b, 0x8003b, 0x900d7, 0x7010d, 0x8006b, 0x8002b, 0x900b7, 0x8000b, 0x8008b, 0x8004b, 0x900f7, 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf, 0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef, 0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df, 0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff
  ]), 9];
  private static fixedDistCodeTab: [Uint32Array, number] = [new Uint32Array([
    0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c, 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000, 0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d, 0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000
  ]), 5];

  constructor(stream: Stream) {
    super();
    const bytes:Uint8Array = stream.getBytes();
    let bytesPos:number = 0;
    this.dict = stream.dict;
    const cmf:number = bytes[bytesPos];
    bytesPos += 1;
    const flg:number = bytes[bytesPos];
    bytesPos += 1;
    if (cmf == -1 || flg == -1){
      error(`Invalid header in flate stream: ${cmf} , ${flg}`);
    }
    if ((cmf & Number_0x0f) !== Number_0x08){
      error(`Unknown compression method in flate stream:  ${cmf} , ${flg}`);
    }
    if ((((cmf << Number_8) + flg) % Number_31) !== 0){
      error(`Bad FCHECK in flate stream:  ${cmf} ,  ${flg}`);
    }
    if (flg & Number_0x20){
      error(`FDICT bit set in flate stream:  ${cmf}  ,  ${flg}`);
    }
    this.bytes = bytes;
    this.bytesPos = bytesPos;
    this.codeSize = 0;
    this.codeBuf = 0;
  }

  private getBits(bits: number): number {
    let codeSize:number = this.codeSize;
    let codeBuf:number = this.codeBuf;
    const bytes:Uint8Array = this.bytes;
    let bytesPos:number = this.bytesPos;
    let b:number;
    while (codeSize < bits) {
      if (bytesPos >= bytes.length){
        error('Bad encoding in flate stream');
      }
      b = bytes[bytesPos];
      bytesPos += 1;
      codeBuf |= b << codeSize;
      codeSize += Number_8;
    }
    const mask:number = (1 << bits) - 1;
    b = codeBuf & mask;
    codeBuf >>= bits;
    codeSize -= bits;
    this.codeSize = codeSize;
    this.codeBuf = codeBuf;
    this.bytesPos = bytesPos;
    return b;
  }

  private getCode(table: [Uint32Array, number]): number {
    const codes:Uint32Array = table[0];
    const maxLen:number = table[1];
    let codeSize:number = this.codeSize;
    let codeBuf:number = this.codeBuf;
    const bytes:Uint8Array = this.bytes;
    let bytesPos:number = this.bytesPos;

    while (codeSize < maxLen) {
      let b:number;
      if (bytesPos >= bytes.length){
        error('Bad encoding in flate stream');
      }
      b = bytes[bytesPos]
      bytesPos += 1
      codeBuf |= (b << codeSize);
      codeSize += Number_8;
    }
    const mask:number = (1 << maxLen) - 1;
    const code: number = codes[codeBuf & mask];
    const codeLen: number = code >> Number_16;
    const codeVal: number = code & Number_0xFFFF;
    if (codeSize == 0 || codeSize < codeLen || codeLen == 0){
      error('Bad encoding in flate stream');
    }
    codeBuf >>= codeLen;
    codeSize -= codeLen;
    this.codeBuf = codeBuf;
    this.codeSize = codeSize;
    this.bytesPos = bytesPos;
    return codeVal;
  }

  private generateHuffmanTable(lengths: Uint8Array): [Uint32Array, number] {
    const n:number = lengths.length;
    let maxLen:number = 0;
    for (let i = 0; i < n; ++i) {
      if (lengths[i] > maxLen) {
        maxLen = lengths[i];
      }
    }
    const size:number = 1 << maxLen;
    let codes:Uint32Array = new Uint32Array(size);
    let len:number = 1;
    let code:number = 0;
    let skip:number = Number_2;
    while (len <= maxLen) {
      let val:number = 0
      while (val < n) {
        if (lengths[val] == len) {
          let code2: number = 0
          let t:number = code
          let i:number = 0
          while (i < len) {
            code2 = (code2 << 1) | (t & 1)
            t >>= 1
            i += 1
          }
          i = code2
          while (i < size) {
            codes[i] = (len << Number_16) | val
            i += skip
          }
          code = code + 1
        }
        val += 1
      }
      len = len + 1
      code <<= 1
      skip <<= 1
    }
    return [codes, maxLen];
  }

  readBlock(): void {
    let litCodeTable:[Uint32Array, number];
    let distCodeTable:[Uint32Array, number];
    let hdr:number = this.getBits(Number_3);
    if ((hdr & 1) !== 0){
      this.eof = true;
    }
    hdr >>= 1;
    if (hdr == 0) {
      const bytes:Uint8Array = this.bytes;
      let bytesPos: number = this.bytesPos;
      if (bytesPos >= bytes.length){
        error('Bad block header in flate stream');
        return;
      }
      let blockLen = bytes[bytesPos];
      bytesPos += 1;
      if (bytesPos >= bytes.length){
        error('Bad block header in flate stream');
        return;
      }
      blockLen |= (bytes[bytesPos] << Number_8);
      bytesPos += 1;
      if (bytesPos >= bytes.length){
        error('Bad block header in flate stream');
        return;
      }
      let check = bytes[bytesPos];
      bytesPos += 1;
      if (bytesPos >= bytes.length){
        error('Bad block header in flate stream');
        return;
      }
      check |= (bytes[bytesPos] << Number_8);
      bytesPos += 1;
      if (check !== (~blockLen & Number_0xFFFF)){
        error('Bad uncompressed block length in flate stream');
        return;
      }
      this.codeBuf = 0;
      this.codeSize = 0;
      const bufferLength:number = this.bufferLength;
      let buffer: Uint8Array = this.ensureBuffer(bufferLength + blockLen);
      const end:number = bufferLength + blockLen;
      this.bufferLength = end;
      for (let n = bufferLength; n < end; ++n) {
        if (bytesPos >= bytes.length) {
          this.eof = true;
          break;
        }
        buffer[n] = bytes[bytesPos];
        bytesPos += 1;
      }
      this.bytesPos = bytesPos;
      return;
    }
    if (hdr == 1) {
      litCodeTable = FlateStream.fixedLitCodeTab;
      distCodeTable = FlateStream.fixedDistCodeTab;
    } else if (hdr == Number_2) {
      const numLitCodes:number = this.getBits(Number_5) + Number_257;
      const numDistCodes:number = this.getBits(Number_5) + 1;
      const numCodeLenCodes:number = this.getBits(Number_4) + Number_4;
      const codeLenCodeLengths:Uint8Array = new Uint8Array(FlateStream.codeLenCodeMap.length);

      for (let i: number = 0; i < numCodeLenCodes; ++i) {
        codeLenCodeLengths[FlateStream.codeLenCodeMap[i]] = this.getBits(Number_3);
      }

      const codeLenCodeTab: [Uint32Array, number] = this.generateHuffmanTable(codeLenCodeLengths);
      let len:number = 0;
      let i:number = 0;
      const codes:number = numLitCodes + numDistCodes;
      let codeLengths:Uint8Array = new Uint8Array(codes);

      while (i < codes) {
        const code:number = this.getCode(codeLenCodeTab);
        let bitsLength: number;
        let bitsOffset: number;
        let what: number;
        switch (code) {
          case Number_16:
            bitsLength = Number_2;
            bitsOffset = Number_3;
            what = len;
            break;
          case Number_17:
            bitsLength = Number_3;
            bitsOffset = Number_3;
            what = 0;
            len = 0;
            break;
          case Number_18:
            bitsLength = Number_7;
            bitsOffset = Number_11;
            what = 0;
            len = 0;
            break;
          default:
            codeLengths[i] = code;
            i += 1;
            len = code;
            continue;
        }
        let repeatLength:number = this.getBits(bitsLength) + bitsOffset;
        while (repeatLength > 0) {
          codeLengths[i] = what;
          i += 1;
          repeatLength -= 1;
        }
      }
      litCodeTable = this.generateHuffmanTable(codeLengths.subarray(0, numLitCodes));
      distCodeTable = this.generateHuffmanTable(codeLengths.subarray(numLitCodes, codes));
    } else {
      error('Unknown block type in flate stream');
      return;
    }
    let limit: number = this.buffer !== null ? this.buffer.length : 0;
    let pos:number = this.bufferLength;
    while (true) {
      let code1:number = this.getCode(litCodeTable);
      if (code1 < Number_256) {
        if (pos + 1 >= limit) {
          this.buffer = this.ensureBuffer(pos + 1);
          limit = this.buffer?.length ?? 0;
        }
        this.buffer[pos] = code1;
        pos += 1;
        continue;
      }
      if (code1 == Number_256) {
        this.bufferLength = pos;
        break;
      }
      code1 -= Number_257;
      code1 = this.lengthDecode[code1];
      let code2:number = code1 >> Number_16;
      if (code2 > 0){
        code2 = this.getBits(code2);
      }
      const len:number = (code1 & Number_0xFFFF) + code2;
      code1 = this.getCode(distCodeTable);
      code1 = this.distDecode[code1];
      code2 = code1 >> Number_16;
      if (code2 > 0){
        code2 = this.getBits(code2);
      }
      const dist:number = (code1 & Number_0xFFFF) + code2;

      if (pos + len >= limit) {
        this.buffer = this.ensureBuffer(pos + len);
        limit = this.buffer?.length ?? 0;
      }

      for (let k = 0; k < len; ++k){
        this.buffer[pos] = this.buffer[pos - dist];
        pos += 1;
      }
    }
  }
}
class JpegStream extends DecodeStream {
  private isAdobeImage: boolean = false;
  private colorTransform: number = 0;
  bitsPerComponent:number;
  getParams:Dict|null;

  constructor(bytes: Uint8Array, dict: Dict, xref: XRef) {
    super();
    this.dict = dict;
    this.isAdobeImage = false;
    this.colorTransform = dict.get('ColorTransform') as number || -1;
    let copyBytes:Uint8Array = bytes
    if (this.isAdobeImages(copyBytes)) {
      this.isAdobeImage = true;
      copyBytes = this.fixAdobeImage(copyBytes);
    }
    this.bytes = copyBytes;
  }

  private isAdobeImages(bytes: Uint8Array): boolean {
    const maxBytesScanned:number = Math.max(bytes.length - Number_16, Number_1024);
    for (let i = 0; i < maxBytesScanned; ++i) {
      if (bytes[i] == Number_0xFF && bytes[i + 1] == Number_0xEE &&
        bytes[i + Number_2] === Number_0x00 && bytes[i + Number_3] === Number_0x0E &&
        bytes[i + Number_4] === Number_0x41 && bytes[i + Number_5] === Number_0x64 &&
        bytes[i + Number_6] === Number_0x6F && bytes[i + Number_7] === Number_0x62 &&
        bytes[i + Number_8] === Number_0x65 && bytes[i + Number_9] === Number_0x00) {
        return true;
      }
      if (bytes[i] === Number_0xFF && bytes[i + 1] === Number_0xC0) {
        break;
      }
    }
    return false;
  }

  private fixAdobeImage(bytes: Uint8Array): Uint8Array {
    const embedMarker:Uint8Array = new Uint8Array([0xFF, 0xEC, 0, 8, 0x45, 0x4D, 0x42, 0x45,0x44, 0]);
    let newBytes:Uint8Array = new Uint8Array(bytes.length + embedMarker.length);
    newBytes.set(bytes, embedMarker.length);
    newBytes[0] = bytes[0];
    newBytes[1] = bytes[1];
    newBytes.set(embedMarker, Number_2);
    return newBytes;
  }

  ensureBuffer(req: number): Uint8Array {
    if (this.bufferLength !== 0) {
      return new Uint8Array();
    }

    try {
      const jpegImage:JpegImage = new JpegImage();
      if (this.colorTransform !== -1) {
        jpegImage.colorTransform = this.colorTransform !== 0;
      }
      jpegImage.parse(this.bytes);
      const width:number = jpegImage.width;
      const height:number = jpegImage.height;
      const data:Uint8Array = jpegImage.getData(width, height);
      this.buffer = data;
      this.bufferLength = data.length;

    } catch (e) {
      error(`JPEG error: ${e}`);
    }
    return new Uint8Array();
  }

  public getIR(): string {
    return bytesToString(this.bytes);
  }

  public getChar():string {
    error('internal error: getChar is not valid on JpegStream');
    return null
  }

  public isNativelySupported(xref: XRef, res: Dict): boolean {
    const cs:AlternateCS = ColorSpace.parse(this.dict.get('ColorSpace'), xref, res) as AlternateCS;
    if (cs.name === 'DeviceGray' || cs.name === 'DeviceRGB') {
      return true;
    }
    if (cs.name === 'DeviceCMYK' && !this.isAdobeImage && this.colorTransform < 1) {
      return true;
    }
    return false;
  }

  public isNativelyDecodable(xref: XRef, res: Dict): boolean {
    const cs:AlternateCS = ColorSpace.parse(this.dict.get('ColorSpace'), xref, res) as AlternateCS;
    const numComps:number = cs.numComps;
    if (numComps === 1 || numComps === Number_3) {
      return true;
    }
    return false;
  }
}
class JpxStream extends DecodeStream {
  constructor(bytes: Uint8Array, dict: Dict) {
    super();
    this.dict = dict;
    this.bytes = bytes;
    //DecodeStream.call(this);
  }

  ensureBuffer(req: number): Uint8Array {
    if (this.bufferLength !== 0) {
      return null;
    }

    const jpxImage:JpxImage = new JpxImage();
    jpxImage.parse(this.bytes);

    const width:number = jpxImage.width;
    const height:number = jpxImage.height;
    const componentsCount:number = jpxImage.componentsCount;

    if (componentsCount !== 1 && componentsCount !== Number_3 && componentsCount !== Number_4) {
      error(`JPX with ${componentsCount} components is not supported`);
    }

    let data:Uint8Array= new Uint8Array(width * height * componentsCount);
    for (let k = 0; k < jpxImage.tiles.length; k++) {
      const tileCompoments:TransformResult[] = jpxImage.tiles[k];
      const tileWidth:number = tileCompoments[0].width;
      const tileHeight:number = tileCompoments[0].height;
      const tileLeft:number = tileCompoments[0].left;
      const tileTop:number = tileCompoments[0].top;

      let dataPosition:number;
      let sourcePosition:number;
      let data0:Uint8Array;
      let data1:Uint8Array;
      let data2:Uint8Array;
      let data3:Uint8Array;
      let rowFeed:number;
      switch (componentsCount) {
        case 1:
          data0 = tileCompoments[0].items;
          dataPosition = width * tileTop + tileLeft;
          rowFeed = width - tileWidth;
          sourcePosition = 0;
          for (let j = 0; j < tileHeight; j++) {
            for (let i = 0; i < tileWidth; i++){
              data[dataPosition] = data0[sourcePosition];
              dataPosition += 1;
              sourcePosition += 1;
            }
            dataPosition += rowFeed;
          }
          break;
        case Number_3:
          data0 = tileCompoments[0].items;
          data1 = tileCompoments[1].items;
          data2 = tileCompoments[Number_2].items;

          dataPosition = (width * tileTop + tileLeft) * Number_3;
          rowFeed = (width - tileWidth) * Number_3;
          sourcePosition = 0;
          for (let j = 0; j < tileHeight; j++) {
            for (let i = 0; i < tileWidth; i++) {
              data[dataPosition] = data0[sourcePosition];
              dataPosition += 1;
              data[dataPosition] = data1[sourcePosition];
              dataPosition += 1;
              data[dataPosition] = data2[sourcePosition];
              dataPosition += 1;
              sourcePosition += 1;
            }
            dataPosition += rowFeed;
          }
          break;
        case Number_4:
          data0 = tileCompoments[0].items;
          data1 = tileCompoments[1].items;
          data2 = tileCompoments[Number_2].items;
          data3 = tileCompoments[Number_3].items;

          dataPosition = (width * tileTop + tileLeft) * Number_4;
          rowFeed = (width - tileWidth) * Number_4;
          sourcePosition = 0;
          for (let j = 0; j < tileHeight; j++) {
            for (let i = 0; i < tileWidth; i++) {
              data[dataPosition] = data0[sourcePosition];
              dataPosition += 1;
              data[dataPosition] = data1[sourcePosition];
              dataPosition += 1;
              data[dataPosition] = data2[sourcePosition];
              dataPosition += 1;
              data[dataPosition] = data3[sourcePosition];
              dataPosition += 1;
              sourcePosition += 1;
            }
            dataPosition += rowFeed;
          }
          break;
        default:
          break;
      }
    }
    this.buffer = data;
    this.bufferLength = data.length;
    return data;
  }
  public getChar(): string {
    error('internal error: getChar is not valid on JpegStream');
    return null;
  }
}

class DecryptStream extends DecodeStream {
  private str: StringStream = null;
  private decrypt: (data:Uint8Array) => Uint8Array = null;

  constructor(str: StringStream, decrypt: (data:Uint8Array) => Uint8Array) {
    super();
    this.str = str;
    this.decrypt = decrypt;
    this.dict = str.dict;
  }

  private static readonly chunkSize: number = 512;
  readBlock(): void {
    let chunk:Uint8Array = this.str.getBytes(DecryptStream.chunkSize);
    if ( chunk.length === 0) {
      this.eof = true;
      return;
    }

    const decrypt:(Uint8Array) => Uint8Array = this.decrypt;
    chunk = decrypt(chunk);

    let bufferLength: number = this.bufferLength;
    const n:number = chunk.length;
    let buffer: Uint8Array = this.ensureBuffer(bufferLength + n);

    for (let i = 0; i < n; i++) {
      buffer[bufferLength] = chunk[i];
      bufferLength += 1;
    }

    this.bufferLength = bufferLength;
  }
}

class Ascii85Stream extends DecodeStream {
  private str: StringStream = null;
  private input: Uint8Array = null;

  constructor(str: StringStream) {
    super();
    this.str = str;
    this.dict = str.dict;
    this.input = new Uint8Array(Number_5);
  }

  readBlock(): void {
    const tildaCode:number = '~'.charCodeAt(0);
    const zCode:number = 'z'.charCodeAt(0);
    const str:StringStream = this.str;

    let c:number = str.getByte();
    while (Lexer.isSpace(String.fromCharCode(c))) {
      c = str.getByte();
    }

    if (c === null || c === tildaCode) {
      this.eof = true;
      return;
    }

    let bufferLength:number = this.bufferLength;
    let buffer: Uint8Array;

    if (c === zCode) {
      buffer = this.ensureBuffer(bufferLength + Number_4);
      for (let i = 0; i < Number_4; ++i) {
        buffer[bufferLength + i] = 0;
      }
      this.bufferLength += Number_4;
    } else {
      let input:Uint8Array = this.input;
      input[0] = c;
      let i: number = 1;
      while (i < Number_5) {
        c = str.getByte();
        while (Lexer.isSpace(String.fromCharCode(c))) {
          c = str.getByte();
        }
        input[i] = c;
        if ((c === null || c === undefined) || c === tildaCode) {
          break;
        }
        i += 1;
      }
      buffer = this.ensureBuffer(bufferLength + (i - 1));
      this.bufferLength += (i - 1);

      if (i < Number_5) {
        for (; i < Number_5; ++i) {
          input[i] = Number_0x21 + Number_84;
        }
        this.eof = true;
      }

      let t:number = 0;
      for (let j = 0; j < Number_5; ++j) {
        t = t * 85 + (input[j] - 0x21);
      }
      let ii = Number_3;
      while (ii >= 0) {
        buffer[bufferLength + ii] = t & Number_0xFF;
        t >>= Number_8;
        ii -= 1;
      }
    }
  }
}

class AsciiHexStream extends DecodeStream {
  private str: StringStream = null;
  private static hexvalueMap: { [key: number]: number } = {
    9: -1,
    32: -1,
    48: 0,
    49: 1,
    50: 2,
    51: 3,
    52: 4,
    53: 5,
    54: 6,
    55: 7,
    56: 8,
    57: 9,
    65: 10,
    66: 11,
    67: 12,
    68: 13,
    69: 14,
    70: 15,
    97: 10,
    98: 11,
    99: 12,
    100: 13,
    101: 14,
    102: 15,
  };
  constructor(str: StringStream) {
    super();
    this.str = str;
    this.dict = str.dict;

  }

  readBlock(): void {
    const gtCode: number = '>'.charCodeAt(0);
    const bytes:Uint8Array = this.str.getBytes();
    let c:number;
    let n:number;
    let decodeLength:number;
    let buffer:Uint8Array = this.ensureBuffer(this.bufferLength + (bytes.length + 1) >> 1);
    let bufferLength:number = this.bufferLength;
    let length: number;
    let i:number = 0;
    while (i < bytes.length) {
      c = AsciiHexStream.hexvalueMap[bytes[i]];
      while (c == -1 && (i + 1) < bytes.length) {
        c = AsciiHexStream.hexvalueMap[bytes[i + 1]] || -1;
        i += 1
      }

      if ((i + 1) < bytes.length && (bytes[i + 1] !== gtCode!)) {
        n = AsciiHexStream.hexvalueMap[bytes[i + 1]] || -1;
        buffer[bufferLength] = c * 16 + n;
        bufferLength += 1;
        i += 1;
      } else {
        if (bytes[i] !== gtCode!) {
          buffer[bufferLength] = c * Number_16;
          bufferLength += 1;
        }
      }
    }
    this.bufferLength = bufferLength;
    this.eof = true;
  }
}

class RunLengthStream extends DecodeStream {
  str?: StringStream = null;
  constructor(str: StringStream) {
    super();
    this.str = str;
    this.dict = str.dict;
  }

  readBlock(): void {
    const repeatHeader:Uint8Array = this.str.getBytes(Number_2);
    if (repeatHeader.length === 0 || repeatHeader.length < 2 || repeatHeader[0] === Number_128) {
      this.eof = true;
      return;
    }

    let bufferLength:number = this.bufferLength;
    let n:number = repeatHeader[0];
    if (n < Number_128) {
      let buffer:Uint8Array = this.ensureBuffer(bufferLength + n + 1);
      buffer[bufferLength] = repeatHeader[1];
      bufferLength += 1;
      if (n > 0) {
        const source:Uint8Array = this.str.getBytes(n);
        buffer.set(source, bufferLength);
        bufferLength += n;
      }
    } else {
      n = 257 - n;
      const b:number = repeatHeader[1];
      let buffer:Uint8Array = this.ensureBuffer(bufferLength + n + 1);
      for (let i = 0; i < n; i++) {
        buffer[bufferLength] = b;
        bufferLength += 1;
      }
    }
    this.bufferLength = bufferLength;
  }
}

class CCITTFaxStream extends DecodeStream {
  private str: StringStream;
  private encoding: number;
  private eoline: boolean;
  private byteAlign: boolean;

  private columns: number;
  private rows: number;
  private eoblock: boolean;
  private black: boolean;
  private codingLine: Uint32Array;
  private refLine: Uint32Array;
  private codingPos: number;
  private row: number;
  private nextLine2D: boolean;
  private inputBits: number;
  private inputBuf: number;
  private outputBits: number;
  private buf: number;
  private err: boolean;
  private EOF:number = 0;
  private ccittEOL: number = - Number_2;
  private static twoDimPass: number = 0;
  private static twoDimHoriz: number = 1;
  private static twoDimVert0: number = Number_2;
  private static twoDimVertR1: number = Number_3;
  private static twoDimVertL1: number = Number_4;
  private static twoDimVertR2: number = Number_5;
  private static twoDimVertL2: number = Number_6;
  private static twoDimVertR3: number = Number_7;
  private static twoDimVertL3: number = Number_8;

  private twoDimTable: number[][] = [
    [-1, -1], [-1, -1], [7, CCITTFaxStream.twoDimVertL3], [7, CCITTFaxStream.twoDimVertR3], [6, CCITTFaxStream.twoDimVertL2], [6, CCITTFaxStream.twoDimVertL2], [6, CCITTFaxStream.twoDimVertR2], [6, CCITTFaxStream.twoDimVertR2], [4, CCITTFaxStream.twoDimPass], [4, CCITTFaxStream.twoDimPass], [4, CCITTFaxStream.twoDimPass], [4, CCITTFaxStream.twoDimPass], [4, CCITTFaxStream.twoDimPass], [4, CCITTFaxStream.twoDimPass], [4, CCITTFaxStream.twoDimPass], [4, CCITTFaxStream.twoDimPass], [3, CCITTFaxStream.twoDimHoriz], [3, CCITTFaxStream.twoDimHoriz], [3, CCITTFaxStream.twoDimHoriz], [3, CCITTFaxStream.twoDimHoriz], [3, CCITTFaxStream.twoDimHoriz], [3, CCITTFaxStream.twoDimHoriz], [3, CCITTFaxStream.twoDimHoriz], [3, CCITTFaxStream.twoDimHoriz], [3, CCITTFaxStream.twoDimHoriz], [3, CCITTFaxStream.twoDimHoriz], [3, CCITTFaxStream.twoDimHoriz], [3, CCITTFaxStream.twoDimHoriz], [3, CCITTFaxStream.twoDimHoriz], [3, CCITTFaxStream.twoDimHoriz], [3, CCITTFaxStream.twoDimHoriz], [3, CCITTFaxStream.twoDimHoriz], [3, CCITTFaxStream.twoDimVertL1], [3, CCITTFaxStream.twoDimVertL1], [3, CCITTFaxStream.twoDimVertL1], [3, CCITTFaxStream.twoDimVertL1], [3, CCITTFaxStream.twoDimVertL1], [3, CCITTFaxStream.twoDimVertL1], [3, CCITTFaxStream.twoDimVertL1], [3, CCITTFaxStream.twoDimVertL1], [3, CCITTFaxStream.twoDimVertL1], [3, CCITTFaxStream.twoDimVertL1], [3, CCITTFaxStream.twoDimVertL1], [3, CCITTFaxStream.twoDimVertL1], [3, CCITTFaxStream.twoDimVertL1], [3, CCITTFaxStream.twoDimVertL1], [3, CCITTFaxStream.twoDimVertL1], [3, CCITTFaxStream.twoDimVertL1], [3, CCITTFaxStream.twoDimVertR1], [3, CCITTFaxStream.twoDimVertR1], [3, CCITTFaxStream.twoDimVertR1], [3, CCITTFaxStream.twoDimVertR1], [3, CCITTFaxStream.twoDimVertR1], [3, CCITTFaxStream.twoDimVertR1], [3, CCITTFaxStream.twoDimVertR1], [3, CCITTFaxStream.twoDimVertR1], [3, CCITTFaxStream.twoDimVertR1], [3, CCITTFaxStream.twoDimVertR1], [3, CCITTFaxStream.twoDimVertR1], [3, CCITTFaxStream.twoDimVertR1], [3, CCITTFaxStream.twoDimVertR1], [3, CCITTFaxStream.twoDimVertR1], [3, CCITTFaxStream.twoDimVertR1], [3, CCITTFaxStream.twoDimVertR1], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0], [1, CCITTFaxStream.twoDimVert0]
  ];
  private whiteTable1: number[][] = [
    [-1, -1], [12, this.ccittEOL], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [11, 1792], [11, 1792], [12, 1984], [12, 2048], [12, 2112], [12, 2176], [12, 2240], [12, 2304], [11, 1856], [11, 1856], [11, 1920], [11, 1920], [12, 2368], [12, 2432], [12, 2496], [12, 2560]
  ];
  private whiteTable2: number[][] = [
    [-1, -1], [-1, -1], [-1, -1], [-1, -1], [8, 29], [8, 29], [8, 30], [8, 30], [8, 45], [8, 45], [8, 46], [8, 46], [7, 22], [7, 22], [7, 22], [7, 22], [7, 23], [7, 23], [7, 23], [7, 23], [8, 47], [8, 47], [8, 48], [8, 48], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [7, 20], [7, 20], [7, 20], [7, 20], [8, 33], [8, 33], [8, 34], [8, 34], [8, 35], [8, 35], [8, 36], [8, 36], [8, 37], [8, 37], [8, 38], [8, 38], [7, 19], [7, 19], [7, 19], [7, 19], [8, 31], [8, 31], [8, 32], [8, 32], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [8, 53], [8, 53], [8, 54], [8, 54], [7, 26], [7, 26], [7, 26], [7, 26], [8, 39], [8, 39], [8, 40], [8, 40], [8, 41], [8, 41], [8, 42], [8, 42], [8, 43], [8, 43], [8, 44], [8, 44], [7, 21], [7, 21], [7, 21], [7, 21], [7, 28], [7, 28], [7, 28], [7, 28], [8, 61], [8, 61], [8, 62], [8, 62], [8, 63], [8, 63], [8, 0], [8, 0], [8, 320], [8, 320], [8, 384], [8, 384], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [7, 27], [7, 27], [7, 27], [7, 27], [8, 59], [8, 59], [8, 60], [8, 60], [9, 1472], [9, 1536], [9, 1600], [9, 1728], [7, 18], [7, 18], [7, 18], [7, 18], [7, 24], [7, 24], [7, 24], [7, 24], [8, 49], [8, 49], [8, 50], [8, 50], [8, 51], [8, 51], [8, 52], [8, 52], [7, 25], [7, 25], [7, 25], [7, 25], [8, 55], [8, 55], [8, 56], [8, 56], [8, 57], [8, 57], [8, 58], [8, 58], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [8, 448], [8, 448], [8, 512], [8, 512], [9, 704], [9, 768], [8, 640], [8, 640], [8, 576], [8, 576], [9, 832], [9, 896], [9, 960], [9, 1024], [9, 1088], [9, 1152], [9, 1216], [9, 1280], [9, 1344], [9, 1408], [7, 256], [7, 256], [7, 256], [7, 256], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7]
  ];
  private blackTable1: number[][] = [
    [-1, -1], [-1, -1], [12, this.ccittEOL], [12, this.ccittEOL], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [11, 1792], [11, 1792], [11, 1792], [11, 1792], [12, 1984], [12, 1984], [12, 2048], [12, 2048], [12, 2112], [12, 2112], [12, 2176], [12, 2176], [12, 2240], [12, 2240], [12, 2304], [12, 2304], [11, 1856], [11, 1856], [11, 1856], [11, 1856], [11, 1920], [11, 1920], [11, 1920], [11, 1920], [12, 2368], [12, 2368], [12, 2432], [12, 2432], [12, 2496], [12, 2496], [12, 2560], [12, 2560], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [12, 52], [12, 52], [13, 640], [13, 704], [13, 768], [13, 832], [12, 55], [12, 55], [12, 56], [12, 56], [13, 1280], [13, 1344], [13, 1408], [13, 1472], [12, 59], [12, 59], [12, 60], [12, 60], [13, 1536], [13, 1600], [11, 24], [11, 24], [11, 24], [11, 24], [11, 25], [11, 25], [11, 25], [11, 25], [13, 1664], [13, 1728], [12, 320], [12, 320], [12, 384], [12, 384], [12, 448], [12, 448], [13, 512], [13, 576], [12, 53], [12, 53], [12, 54], [12, 54], [13, 896], [13, 960], [13, 1024], [13, 1088], [13, 1152], [13, 1216], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64]
  ];
  private blackTable2: number[][] = [
    [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [11, 23], [11, 23], [12, 50], [12, 51], [12, 44], [12, 45], [12, 46], [12, 47], [12, 57], [12, 58], [12, 61], [12, 256], [10, 16], [10, 16], [10, 16], [10, 16], [10, 17], [10, 17], [10, 17], [10, 17], [12, 48], [12, 49], [12, 62], [12, 63], [12, 30], [12, 31], [12, 32], [12, 33], [12, 40], [12, 41], [11, 22], [11, 22], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [12, 128], [12, 192], [12, 26], [12, 27], [12, 28], [12, 29], [11, 19], [11, 19], [11, 20], [11, 20], [12, 34], [12, 35], [12, 36], [12, 37], [12, 38], [12, 39], [11, 21], [11, 21], [12, 42], [12, 43], [10, 0], [10, 0], [10, 0], [10, 0], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12]
  ];
  private blackTable3: number[][] = [
    [-1, -1], [-1, -1], [-1, -1], [-1, -1], [6, 9], [6, 8], [5, 7], [5, 7], [4, 6], [4, 6], [4, 6], [4, 6], [4, 5], [4, 5], [4, 5], [4, 5], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2]
  ];

  constructor(str: StringStream, params: Dict) {
    super();
    this.str = str;
    let copyParams:Dict = params || new Dict();
    this.encoding = copyParams.get('K') as number || 0;
    this.eoline = copyParams.get('EndOfLine') as boolean || false;
    this.byteAlign = copyParams.get('EncodedByteAlign') as boolean || false;
    this.columns = copyParams.get('Columns') as number || 1728;
    this.rows = copyParams.get('Rows') as number || 0;
    this.eoblock = copyParams.get('EndOfBlock') as boolean;
    this.black = copyParams.get('BlackIs1') as boolean || false;
    this.codingLine = new Uint32Array(this.columns + 1);
    this.refLine = new Uint32Array(this.columns + Number_2);
    this.codingLine[0] = this.columns;
    this.codingPos = 0;

    this.row = 0;
    this.nextLine2D = this.encoding < 0;
    this.inputBits = 0;
    this.inputBuf = 0;
    this.outputBits = 0;
    this.buf = this.EOF;
    this.dict = str.dict;
    let code1:number = this.lookBits(Number_12);
    while (code1 === 0) {
      this.eatBits(1);
    }
    if (code1 == 1) {
      this.eatBits(Number_12);
    }
    if (this.encoding > 0) {
      this.nextLine2D = !(this.lookBits(1) !== 0);
      this.eatBits(1);
    }
  }

  readBlock() {
    while (!this.eof) {
      let c: number = this.lookChars();
      this.buf = this.EOF;
      this.ensureBuffer(this.bufferLength + 1);
      this.buffer[this.bufferLength] = c;
      this.bufferLength += 1;
    }
  }

  addPixels(a1: number, blackPixels: number) {
    let codingLine:Uint32Array = this.codingLine;
    let codingPos:number = this.codingPos;
    let aValue:number = a1;
    if (a1 > codingLine[codingPos]) {
      if (a1 > this.columns) {
        warn('row is wrong length');
        this.err = true;
        aValue = this.columns;
      }
      if ((codingPos & 1) ^ blackPixels) {
        codingPos += 1;
      }
      codingLine[codingPos] = aValue;
    }
    this.codingPos = codingPos;
  }

  addPixelsNeg(a1: number, blackPixels: number) {
    let codingLine:Uint32Array = this.codingLine;
    let codingPos:number = this.codingPos;
    let aValue:number = a1;
    if (a1 > codingLine[codingPos]) {
      if (a1 > this.columns) {
        warn('row is wrong length');
        this.err = true;
        aValue = this.columns;
      }
      if ((codingPos & 1) ^ blackPixels){
        codingPos += 1;
      }
      codingLine[codingPos] = aValue;
    } else if (a1 < codingLine[codingPos]) {
      if (a1 < 0) {
        warn('invalid code');
        this.err = true;
        aValue = 0;
      }
      while (codingPos > 0 && a1 < codingLine[codingPos - 1]){
        codingPos -= 1;
      }
      codingLine[codingPos] = aValue;
    }

    this.codingPos = codingPos;
  }

  lookChars(): number {
    if (this.buf !== EOF){
      return this.buf;
    }
    let refLine:Uint32Array = this.refLine;
    let codingLine:Uint32Array = this.codingLine;
    const columns:number = this.columns;
    let refPos:number;
    let blackPixels:number;
    if (this.outputBits === 0) {
      if (this.eof){
        return null;
      }
      this.err = false;
      let code1:number | object;
      let code2:number;
      let code3:number;
      if (this.nextLine2D) {
        let i: number = 0;
        while (codingLine[i] < columns) {
          refLine[i] = codingLine[i];
          i += 1;
        }
        refLine[i] = columns;
        i += 1;
        refLine[i] = columns;
        codingLine[0] = 0;
        this.codingPos = 0;
        refPos = 0;
        blackPixels = 0;
        while (codingLine[this.codingPos] < columns) {
          code1 = this.getTwoDimCode();
          switch (code1) {
            case CCITTFaxStream.twoDimPass:
              this.addPixels(refLine[refPos + 1], blackPixels);
              if (refLine[refPos + 1] < columns) {
                refPos += Number_2;
              }
              break;
            case CCITTFaxStream.twoDimHoriz:
              code2 = 0;
              code1 = code2;
              if (blackPixels !== 0) {
                do {
                  code3 = this.getBlackCode();
                  code1 += code3;
                } while (code3 >= Number_64);
                do {
                  code3 = this.getWhiteCode();
                  code2 += code3;
                } while (code3 >= Number_64);
              } else {
                do {
                  code3 = this.getWhiteCode();
                  code1 += code3;
                } while (code3 >= Number_64);
                do {
                  code3 = this.getBlackCode();
                  code2 += code3;
                } while (code3 >= Number_64);
              }
              this.addPixels(codingLine[this.codingPos] + code1, blackPixels);
              if (codingLine[this.codingPos] < columns) {
                this.addPixels(+codingLine[this.codingPos] + code2, blackPixels ^ 1);
              }
              while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
                refPos += Number_2;
              }
              break;
            case CCITTFaxStream.twoDimVertR3:
              this.addPixels(refLine[refPos] + Number_3, blackPixels);
              blackPixels ^= 1;
              if (codingLine[this.codingPos] < columns) {
                refPos += 1;
                while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
                  refPos += Number_2;
                }
              }
              break;
            case CCITTFaxStream.twoDimVertR2:
              this.addPixels(refLine[refPos] + Number_2, blackPixels);
              blackPixels ^= 1;
              if (codingLine[this.codingPos] < columns) {
                refPos += 1;
                while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
                  refPos += Number_2;
                }
              }
              break;
            case CCITTFaxStream.twoDimVertR1:
              this.addPixels(refLine[refPos] + 1, blackPixels);
              blackPixels ^= 1;
              if (codingLine[this.codingPos] < columns) {
                refPos += 1;
                while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
                  refPos += Number_2;
                }
              }
              break;
            case CCITTFaxStream.twoDimVert0:
              this.addPixels(refLine[refPos], blackPixels);
              blackPixels ^= 1;
              if (codingLine[this.codingPos] < columns) {
                refPos += 1;
                while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
                  refPos += Number_2;
                }
              }
              break;
            case CCITTFaxStream.twoDimVertL3:
              this.addPixelsNeg(refLine[refPos] - Number_3, blackPixels);
              blackPixels ^= 1;
              if (codingLine[this.codingPos] < columns) {
                if (refPos > 0) {
                  refPos -= 1;
                } else {
                  refPos += 1;
                }
                while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
                  refPos += Number_2;
                }
              }
              break;
            case CCITTFaxStream.twoDimVertL2:
              this.addPixelsNeg(refLine[refPos] - Number_2, blackPixels);
              blackPixels ^= 1;
              if (codingLine[this.codingPos] < columns) {
                if (refPos > 0) {
                  refPos -= 1;
                } else {
                  refPos += 1;
                }
                while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
                  refPos += Number_2;
                }
              }
              break;
            case CCITTFaxStream.twoDimVertL1:
              this.addPixelsNeg(refLine[refPos] - 1, blackPixels);
              blackPixels ^= 1;
              if (codingLine[this.codingPos] < columns) {
                if (refPos > 0) {
                  refPos -= 1;
                } else {
                  refPos += 1;
                }
                while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
                  refPos += Number_2;
                }
              }
              break;
            case EOF:
              this.addPixels(columns, 0);
              this.eof = true;
              break;
            default:
              warn("bad 2d code");
              this.addPixels(columns, 0);
              this.err = true;
              break;
          }
        }
      } else {
        codingLine[0] = 0;
        this.codingPos = 0;
        blackPixels = 0;
        while (codingLine[this.codingPos] < columns) {
          code1 = 0;
          if (blackPixels !== 0) {
            do {
              code3 = this.getBlackCode()
              code1 += code3;
	      } while (code3 >= Number_64);
          } else {
            do {
              code3 = this.getWhiteCode()
              code1 += code3;
            } while (code3 >= Number_64);
          }
          this.addPixels(codingLine[this.codingPos] + code1, blackPixels);
          blackPixels ^= 1;
        }
      }

      if (this.byteAlign){
        this.inputBits &= ~ Number_7;
      }

      let gotEOL:boolean = false;

      if (!this.eoblock && this.row === this.rows - 1) {
        this.eof = true;
      } else {
        code1 = this.lookBits(Number_12);
        while (code1 === 0) {
          this.eatBits(1);
          code1 = this.lookBits(Number_12);
        }
        if (code1 === 1) {
          this.eatBits(Number_12);
          gotEOL = true;
        } else if (code1 === EOF) {
          this.eof = true;
        }
      }

      if (this.black) {
        this.buf = (this.buf as number) ^ Number_0xFF;
      }
    }
    return this.buf;
  }
  findTableCode(start: number, end: number, table: number[][], limit?: number): [boolean, number, boolean] {
    const limitValue: number = limit || 0;
    for (let i = start; i <= end; ++i) {
      let code:number = this.lookBits(i);
      if (code === EOF) {
        return [true, 1, false];
      }
      if (i < end) {
        code <<= end - i;
      }
      if (!limitValue || code >= limitValue) {
        const p:number[] = table[code - limitValue];
        if (p[0] === i) {
          this.eatBits(i);
          return [true, p[1], true];
        }
      }
    }
    return [false, 0, false];
  }

  private getTwoDimCode(): number | object{
    let code:number = 0;
    let p:number[];
    if (this.eoblock) {
      code = this.lookBits(Number_7);
      p = this.twoDimTable[code];
      if (p.length > 0 && p[0] > 0) {
        this.eatBits(p[0]);
        return p[1];
      }
    } else {
      let result:[boolean, number, boolean] = this.findTableCode(1, Number_7, this.twoDimTable);
      if (result[0] && result[Number_2]){
        return result[1];
      }
    }
    warn('Bad two dim code');
    return EOF;
  }

  private getWhiteCode(): number {
    let code:number = 0;
    let p : number[];
    let n : number;
    if (this.eoblock) {
      code = this.lookBits(Number_12);
      if (code == EOF){
        return 1;
      }
      if ((code >> Number_5) == 0){
        p = this.whiteTable1[code];
      }
      else{
        p = this.whiteTable2[code >> Number_3];
      }
      if (p[0] > 0) {
        this.eatBits(p[0]);
        return p[1];
      }
    } else {
      let result:[boolean, number, boolean] = this.findTableCode(1, Number_9, this.whiteTable2);
      if (result[0]){
        return result[1];
      }
      result = this.findTableCode(Number_11, Number_12, this.whiteTable1);
      if (result[0]){
        return result[1];
      }
    }
    warn('bad white code');
    this.eatBits(1);
    return 1;
  }

  private getBlackCode(): number {
    let code:number;
    let p :number[];
    if (this.eoblock) {
      code = this.lookBits(Number_13);
      if (code == EOF){
        return 1;
      }
      if ((code >> Number_7) == 0){
        p = this.blackTable1[code];
      }
      else if ((code >> Number_9) == 0 && (code >> Number_7) !== 0){
        p = this.blackTable2[(code >> 1) - 64];
      }
      else{
        p = this.blackTable3[code >> Number_7];
      }

      if (p[0] > 0) {
        this.eatBits(p[0]);
        return p[1];
      }
    } else {
      let result:[boolean, number, boolean] = this.findTableCode(Number_2, 6, this.blackTable3);
      if (result[0]){
        return result[1];
      }
      result = this.findTableCode(Number_7, Number_12, this.blackTable2, Number_64);
      if (result[0]){
        return result[1];
      }
      result = this.findTableCode(Number_10, Number_13, this.blackTable1);
      if (result[0]){
        return result[1];
      }
    }
    warn('bad black code');
    this.eatBits(1);
    return 1;
  }

  private lookBits(n: number): number {
    let c:number;
    while (this.inputBits < n) {
      const byte:number = this.str.getByte();
      if (byte !== null) {
        c = byte;
        this.inputBuf = (this.inputBuf << Number_8) + c;
        this.inputBits += Number_8;
      }else{
        if (this.inputBits == 0){
          return 0;
        }
        return ((this.inputBuf << (n - this.inputBits)) & ( Number_0xFFFF >> (Number_16 - n)));
      }
    }
    return (this.inputBuf >> (this.inputBits - n)) & ( Number_0xFFFF >> (Number_16 - n));
  }

  private eatBits(n: number): void {
    this.inputBits -= n
    if (this.inputBits < 0) {
      this.inputBits = 0;
    }
  }
}

interface LzwState {
  prevCode: number;
  earlyChange: boolean;
  codeLength: number;
  nextCode: number;
  dictionaryValues: Uint8Array;
  dictionaryLengths: Uint16Array;
  dictionaryPrevCodes: Uint16Array;
  currentSequence: Uint8Array;
  currentSequenceLength: number;
}
class LZWStream extends DecodeStream {
  private str: StringStream = null;
  private lastCode: number | null;
  private cachedData: number = 0;
  private bitsCached: number = 0;
  private lzwState: LzwState | undefined;

  constructor(str: StringStream, earlyChange: boolean) {
    super();
    this.str = str;
    this.dict = str.dict;
    const maxLzwDictionarySize:number = 4096;
    let lzwState: LzwState = {
      prevCode: 0,
      earlyChange: earlyChange,
      codeLength: 9,
      nextCode: 258,
      dictionaryValues: new Uint8Array(maxLzwDictionarySize),
      dictionaryLengths: new Uint16Array(maxLzwDictionarySize),
      dictionaryPrevCodes: new Uint16Array(maxLzwDictionarySize),
      currentSequence: new Uint8Array(maxLzwDictionarySize),
      currentSequenceLength: 0,
    };

    for (let i = 0; i < Number_256; ++i) {
      lzwState.dictionaryValues[i] = i;
      lzwState.dictionaryLengths[i] = 1;
    }
    this.lzwState = lzwState;
  }

  readBits(n: number): number | null {
    let bitsCached: number = this.bitsCached;
    let cachedData: number = this.cachedData;

    while (bitsCached < n) {
      const c:number = this.str.getByte();
      if (c == null) {
        this.eof = true;
        return null;
      }
      cachedData = (cachedData << Number_8) | c;
      bitsCached += Number_8;
    }
    this.bitsCached = bitsCached - n;
    this.cachedData = cachedData;
    this.lastCode = null;
    return (cachedData >>> bitsCached) & ((1 << n) - 1);
  }

  readBlock(): void {
    const blockSize:number = 512;
    let estimatedDecodedSize:number = blockSize * Number_2;
    const decodedSizeDelta:number = blockSize;
    let i:number;
    let j:number;
    let q:number;
    let lzwState:LzwState = this.lzwState;
    if (lzwState !== null && lzwState !== undefined){
      return;
    }
    let earlyChange:boolean = lzwState.earlyChange;
    let nextCode:number = lzwState.nextCode;
    let dictionaryValues: Uint8Array= lzwState.dictionaryValues;
    let dictionaryLengths:Uint16Array = lzwState.dictionaryLengths;
    let dictionaryPrevCodes:Uint16Array = lzwState.dictionaryPrevCodes;
    let codeLength:number = lzwState.codeLength;
    let prevCode:number = lzwState.prevCode;
    let currentSequence:Uint8Array = lzwState.currentSequence;
    let currentSequenceLength:number = lzwState.currentSequenceLength;
    let decodedLength: number = 0;
    let currentBufferLength: number = this.bufferLength;
    let buffer:Uint8Array = this.ensureBuffer(this.bufferLength + estimatedDecodedSize);

    for (i = 0; i < blockSize; i++) {
      const code:number = this.readBits(codeLength);
      const hasPrev:boolean = currentSequenceLength > 0;

      if (code < 256) {
        currentSequence[0] = code;
        currentSequenceLength = 1;
      }
      else if (code >= 258) {
        if (code < nextCode) {
          currentSequenceLength = dictionaryLengths[code];
          j = currentSequenceLength - 1;
          q = code;
          while (j >= 0) {
            currentSequence[j] = dictionaryValues[q];
            q = dictionaryPrevCodes[q];
            j -= 1
          }
        } else {
          currentSequence[currentSequenceLength] = currentSequence[0];
          currentSequenceLength += 1;
        }
      }
      else if (code === 256) {
        codeLength = Number_9;
        nextCode = Number_258;
        currentSequenceLength = 0;
        continue;
      }
      else {
        this.eof = true;
        this.lzwState = null;
        break;
      }

      if (hasPrev) {
        dictionaryPrevCodes[nextCode] = prevCode;
        dictionaryLengths[nextCode] = dictionaryLengths[prevCode] + 1;
        dictionaryValues[nextCode] = currentSequence[0];
        nextCode += 1;

        const logB:number = Math.log(nextCode + Number(earlyChange)) / 0.6931471805599453 + 1;
        const min:number = Math.min(logB, Number_12);
        codeLength = (nextCode + Number(earlyChange)) & (nextCode + Number(earlyChange) - 1) ?
          codeLength : min;
      }
      prevCode = code;
      decodedLength += currentSequenceLength;
      if (estimatedDecodedSize < decodedLength) {
        do {
          estimatedDecodedSize += decodedSizeDelta;
        } while (estimatedDecodedSize < decodedLength);
        buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize);
      }

      for (j = 0; j < currentSequenceLength; j++) {
        buffer[currentBufferLength] = currentSequence[j];
        currentBufferLength += 1;
      }
    }

    lzwState.nextCode = nextCode;
    lzwState.codeLength = codeLength;
    lzwState.prevCode = prevCode;
    lzwState.currentSequenceLength = currentSequenceLength;
    this.bufferLength = currentBufferLength;
  }
}

class MessageHandler {
  name: string;
  comObj: FakeWorker;
  callbackIndex: number = 1;
  callbacks:Map<number,(data:object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name) => void> = new Map<number,(data:object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name) => void>();
  actionHandler: Map<string, object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name> = new Map();
  constructor(name: string, comObj: FakeWorker) {
    this.name = name;
    this.comObj = comObj;

    this.actionHandler.set('console_log', [(data: string): void => {
      //PdfJS_window.console.log(data);
    }]);

    this.actionHandler.set('console_error',
      [(data: string): void => {
        //PdfJS_window.console.error(data);
      }]);

    comObj.onmessage = (event: EventMessage) => {
      const data: EventMessageData = event.data;

      if (data.isReply) {
        const callbackId:number = data.callbackId;
        if (this.callbacks.has(callbackId)) {
          const callback: (data:object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name) => void = this.callbacks.get(callbackId);
          this.callbacks.set(callbackId, null);
          callback(data.data);
        } else {
          error(`Cannot resolve callback ${callbackId}`);
        }
      } else if (this.actionHandler.get(data.action) !== null) {
        const action:object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = this.actionHandler.get(data.action);
        let method = action[0];
        if (data.callbackId !== null && data.callbackId !== undefined) {
          const promise:PDFPromise = new PDFPromise();
          promise.then((resolvedData) => {
            let message: EventMessageData = new EventMessageData()
            message.isReply = true
            message.callbackId = data.callbackId
            message.data = resolvedData
            this.comObj.postMessage(message)
          });
          method.call(action[1],data.data, promise);
        } else {
          method.call(action[1],data.data,null);
        }
      } else {
        error(`Unknown action from worker: ${data.action}`);
      }
    };
  }


  on(actionName: string, handler: (data: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name, promise?: PDFPromise) => void, scope?: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name): void {
    const actionHandler: Map<string, object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name> = this.actionHandler;
    if (actionHandler.get(actionName) !== null && actionHandler.get(actionName) !== undefined) {
      error(`There is already an actionName called "${actionName}"`);
    }
    actionHandler.set(actionName,[handler, scope]);
  }

  send(actionName: string, data: object, callback: (data: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name) => void = null): void {
    let message: EventMessageData = new EventMessageData(actionName,data);
    if (callback !== null && callback !== undefined){
      this.callbackIndex += 1;
      let callbackId = this.callbackIndex;
      this.callbacks.set(callbackId,callback);
      message.callbackId = callbackId;
    }
    this.comObj.postMessage(message);
  }

  private error(message: string): void {
    console.error(message);
  }
}

interface CodeObj {
  children: CodeObj[]
  index: number
}

interface ImageData {
  width: number
  height: number
  data: Uint8Array
}

interface FrameComponent {
  h: number;
  v: number;
  pred?:number;
  blocksPerLine?: number;
  blocksPerColumn?: number;
  blocks?: Int32Array[][];
  quantizationTable:Int32Array;
  huffmanTableDC?: CodeObj[];
  huffmanTableAC?: CodeObj[];
}
interface JpegFrame {
  progressive?: boolean;
  precision?: number;
  scanLines?: number;
  samplesPerLine?: number;
  components?:{ [componentId: string]: FrameComponent } | [];
  componentsOrder?:number[];
  maxH?: number;
  maxV?: number;
  mcusPerLine?: number;
  mcusPerColumn?: number;
}

interface Jfif {
  version: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
  densityUnits: number;
  xDensity: number;
  yDensity: number;
  thumbWidth: number;
  thumbHeight: number;
  thumbData: Uint8Array;
}

interface Adobe {
  version: number;
  flags0: number;
  flags1: number;
  transformCode: number;
}

interface ImageComponent {
  lines: Uint8Array[];
  scaleX: number;
  scaleY: number;
}

class JpegImage {
  private dctZigZag: Int32Array = new Int32Array([
    0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63
  ]);

  private dctCos1: number = 4017;
  private dctSin1: number = 799;
  private dctCos3: number = 3406;
  private dctSin3: number = 2276;
  private dctCos6: number = 1567;
  private dctSin6: number = 3784;
  private dctSqrt2: number = 5793;
  private dctSqrt1d2: number = 2896;

  public width: number = 0;
  public height: number = 0;
  public jfif: Jfif;
  public adobe: Adobe;
  public components: ImageComponent[];
  public colorTransform?: boolean;

  constructor() {
  }

  private buildHuffmanTable(codeLengths: Uint8Array, values: Uint8Array): CodeObj[] {
    let k:number = 0;
    let code: CodeObj[] = new Array<CodeObj>();
    let length:number = Number_16;
    while (length > 0 && codeLengths[length - 1] === 0) {
      length -= 1;
    }
    code.push({ children: new Array<CodeObj>(), index: 0 });
    let p: CodeObj = code[0];
    let q: CodeObj;
    for (let i = 0; i < length; i++) {
      for (let j = 0; j < codeLengths[i]; j++) {
        p = code.pop();
        p.children[p.index] = { children: new Array<CodeObj>(), index: values[k] };
        while (p.index > 0) {
          p = code.pop();
        }
        p.index += 1;
        code.push(p);
        while (code.length <= i) {
          q = { children: new Array<CodeObj>(), index: 0 };
          code.push(q);
          p.children[p.index] = q;
          p = q;
        }
        k += 1;
      }
      if (i + 1 < length) {
        q = { children: new Array<CodeObj>(), index: 0 };
        code.push(q);
        p.children[p.index] = q;
        p = q;
      }
    }
    return code[0].children;
  }

  private decodeScan(data:Uint8Array, offset:number, frame:JpegFrame, components:FrameComponent[], resetInterval:number, spectralStart:number, spectralEnd:number, successivePrev:number, successive:number):number {
    let copyResetInterval: number = resetInterval
    const precision:number = frame.precision;
    const samplesPerLine:number = frame.samplesPerLine;
    const scanLines:number = frame.scanLines;
    const mcusPerLine:number = frame.mcusPerLine;
    const progressive:boolean = frame.progressive;
    const maxH:number = frame.maxH;
    const maxV:number = frame.maxV;

    let copyOffset:number = offset;
    const startOffset:number = offset;
    let bitsData:number = 0;
    let bitsCount:number = 0;

    let readBit = ():number => {
      if (bitsCount > 0) {
        bitsCount -= 1;
        return (bitsData >> bitsCount) & 1;
      }
      bitsData = data[copyOffset];
      copyOffset += 1;
      if (bitsData == Number_0xFF) {
        let nextByte:number = data[copyOffset];
        copyOffset += 1;
        if (nextByte !== 0) {
          error(`unexpected marker: ${((bitsData << Number_8))}`);
        }
      }
      bitsCount = Number_7;
      return bitsData >>> Number_7;
    }

    let decodeHuffman = (tree: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[]):number | null => {
      let node: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = tree;
      let bit: number = readBit();
      while (bit !== null) {
        let currentNode: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = node[bit];
        if (Array.isArray(currentNode)) {
          node = currentNode;
        } else if (isNum(currentNode)) {
          return currentNode as number;
        } else {
          error('invalid huffman sequence');
        }
      }
      return null;
    }

    let receive = (length: number):number | null => {
      let n:number = 0;
      let remainingLength: number = length
      while (remainingLength > 0) {
        const bit:number = readBit();
        if (bit === null) {
          return null;
        }
        n = (n << 1) | bit;
        remainingLength -= 1;
      }
      return n;
    }

    let receiveAndExtend = (length: number):number => {
      let n:number = receive(length);
      if (n >= 1 << (length - 1)){
        return n;
      }
      return n + (-1 << length) + 1;
    }

    let decodeBaseline = (component:FrameComponent, zz:number[]):void => {
      let copyComp: FrameComponent = component;
      let copyZz: number[] = zz;
      let t:number = decodeHuffman(copyComp.huffmanTableDC);
      let diff = t === 0 ? 0 : receiveAndExtend(t);
      copyComp.pred += diff;
      copyZz[0] = copyComp.pred;
      let k:number = 1;
      while (k < Number_64) {
        const rs:number = decodeHuffman(copyComp.huffmanTableAC);
        const s:number = rs & Number_15;
        const r:number = rs >> Number_4;
        if (s === 0) {
          if (r < Number_15){
            break;
          }
          k += Number_16;
          continue;
        }
        k += r;
        let z:number = this.dctZigZag[k];
        copyZz[z] = receiveAndExtend(s);
        k += 1;
      }
    }

    let decodeDCFirst = (component:FrameComponent, zz:number[]):void => {
      let copyComp: FrameComponent = component;
      let copyZz: number[] = zz
      let t:number = decodeHuffman(copyComp.huffmanTableDC);
      let diff:number = t === 0 ? 0 : (receiveAndExtend(t) << successive);
      copyZz[0] = (copyComp.pred + diff);
      copyComp.pred = copyZz[0];
    }

    let decodeDCSuccessive = (component:FrameComponent, zz:number[]) => {
      let copyZz: number[] = zz
      copyZz[0] |= readBit() << successive;
    }

    let eobrun:number = 0;

    let decodeACFirst = (component:FrameComponent, zz:number[]) => {
      let copyZz: number[] = zz
      if (eobrun > 0) {
        eobrun -= 1;
        return;
      }
      let k:number = spectralStart;
      let e:number = spectralEnd;
      while (k <= e) {
        let rs:number = decodeHuffman(component.huffmanTableAC);
        let s:number = rs & Number_15;
        let r:number = rs >> Number_4;
        if (s === 0) {
          if (r < Number_15) {
            eobrun = receive(r) + (1 << r) - 1;
            break;
          }
          k += Number_16;
          continue;
        }
        k += r;
        let z:number = this.dctZigZag[k];
        copyZz[z] = receiveAndExtend(s) * (1 << successive);
        k += 1;
      }
    }

    let successiveACState:number = 0;
    let successiveACNextValue:number;
    let decodeACSuccessive = (component:FrameComponent, zz:number[]):void => {
      let copyZz: number[] = zz
      let k:number = spectralStart;
      const e:number = spectralEnd;
      let r:number = 0;
      while (k <= e) {
        const z:number = this.dctZigZag[k];
        switch (successiveACState) {
          case 0:
            const rs:number = decodeHuffman(component.huffmanTableAC);
            const s:number = rs & Number_15;
            r = rs >> Number_4;
            if (s === 0) {
              if (r < Number_15) {
                eobrun = receive(r) + (1 << r);
                successiveACState = Number_4;
              } else {
                r = Number_16;
                successiveACState = 1;
              }
            } else {
              if (s !== 1){
                error("invalid ACn encoding");
              }
              successiveACNextValue = receiveAndExtend(s);
              successiveACState = r !== 0 ? Number_2 : Number_3;
            }
            continue;
          case 1: break;
          case Number_2:
            if (copyZz[z] !== 0){
              copyZz[z] += (readBit() << successive);
            }
            else {
              r -= 1;
              if (r === 0){
                successiveACState = successiveACState == Number_2 ? Number_3 : 0;
              }
            }
            break;
          case Number_3:
            if (copyZz[z] !== 0){
              copyZz[z] += (readBit() << successive);
            }
            else {
              copyZz[z] = successiveACNextValue << successive;
              successiveACState = 0;
            }
            break;
          case Number_4:
            if (copyZz[z] !== 0){
              copyZz[z] += (readBit() << successive);
            }
            break;
          default:break;
        }
        k += 1;
      }
      if (successiveACState === Number_4) {
        eobrun -= 1;
        if (eobrun === 0){
          successiveACState = 0;
        }
      }
    }

    let decodeMcu = (component:FrameComponent, decode:(component: FrameComponent, comBlock: number[]) => void, mcu:number, row:number, col:number):void => {
      const mcuRow:number = (mcu / mcusPerLine);
      const mcuCol:number = mcu % mcusPerLine;
      const blockRow:number = mcuRow * component.v + row;
      const blockCol:number = mcuCol * component.h + col;
      const comBlock: number[] = Array.from(component.blocks[blockRow][blockCol]);
      decode(component, comBlock);
    }

    let decodeBlock = (component:FrameComponent, decode:(component: FrameComponent, comBlock: number[]) => void, mcu:number) => {
      const blockRow:number = (mcu / component.blocksPerLine) | 0;
      const blockCol:number = mcu % component.blocksPerLine;
      const comBlock: number[] = Array.from(component.blocks[blockRow][blockCol]);
      decode(component, comBlock);
    }

    let componentsLength:number = components.length;
    let decodeFn: (FrameComponent:object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name, data: number[]) => void;
    if (progressive) {
      if (spectralStart === 0){
        decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive;
      }
      else{
        decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive;
      }
    } else {
      decodeFn = decodeBaseline;
    }

    let mcu:number = 0;
    let marker:number;
    let mcuExpected:number;
    if (componentsLength == 1) {
      mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn;
    } else {
      mcuExpected = mcusPerLine * frame.mcusPerColumn;
    }
    if (copyResetInterval === 0) {
      copyResetInterval = mcuExpected;
    }

    let h:number;
    let v:number;
    while (mcu < mcuExpected) {
      for (let i = 0; i < componentsLength; i++){
        const component: FrameComponent = components[i]
        component.pred = 0
      }
      eobrun = 0;
      if (componentsLength == 1) {
        let component: FrameComponent = components[0]
        for (let n = 0; n < resetInterval; n++) {
          decodeBlock(component, decodeFn, mcu);
          mcu += 1;
        }
      } else {
        for (let n = 0; n < resetInterval; n++) {
          for (let i = 0; i < componentsLength; i++) {
            const component: FrameComponent = components[i]
            h = component.h;
            v = component.v;
            for (let j = 0; j < v; j++) {
              for (let k = 0; k < h; k++) {
                decodeMcu(component, decodeFn, mcu, j, k);
              }
            }
          }
          mcu += 1;
        }
      }
      bitsCount = 0;
      marker = (data[offset] << Number_8) | data[offset + 1];
      if (marker <= Number_0xFF00) {
        error('marker was not found');
      }
      if (marker >= Number_0xFFD0 && marker <= Number_0xFFD7) {
        copyOffset += Number_2;
      }
      else{
        break;
      }
    }
    return copyOffset - startOffset;
  }

  private buildComponentData(frame:JpegFrame, component:FrameComponent):Uint8Array[] {
    let lines: Uint8Array[] = new Array<Uint8Array>();
    let blocksPerLine:number = component.blocksPerLine;
    let blocksPerColumn:number = component.blocksPerColumn;
    let samplesPerLine:number = blocksPerLine << Number_3;
    let R:Int32Array = new Int32Array(Number_64);
    let r:Uint8Array = new Uint8Array(Number_64);

    let quantizeAndInverse = (zz:Int32Array, dataOut:Uint8Array, dataIn:Int32Array):void => {
      let copyDataOut: Uint8Array = dataOut
      const qt:Int32Array = component.quantizationTable;
      let v0: number
      let v1: number
      let v2: number
      let v3: number
      let v4: number
      let v5: number
      let v6: number
      let v7: number
      let t: number
      let p:Int32Array = dataIn

      for (i = 0; i < Number_64; i++){
        p[i] = zz[i] * qt[i];
      }
      for (i = 0; i < Number_8; ++i) {
        let row:number = Number_8 * i;
        if (p[1 + row] == 0 && p[Number_2 + row] == 0 && p[Number_3 + row] == 0 &&
          p[ Number_4 + row] == 0 && p[Number_5 + row] == 0 && p[Number_6 + row] == 0 &&
          p[ Number_7 + row] == 0) {
          t = (this.dctSqrt2 * p[0 + row] + Number_512) >> Number_10;
          p[0 + row] = t;
          p[1 + row] = t;
          p[Number_2 + row] = t;
          p[Number_3 + row] = t;
          p[Number_4 + row] = t;
          p[Number_5 + row] = t;
          p[Number_6 + row] = t;
          p[Number_7 + row] = t;
          continue;
        }

        v0 = (this.dctSqrt2 * p[0 + row] + Number_128) >> Number_8;
        v1 = (this.dctSqrt2 * p[Number_4 + row] + Number_128) >> Number_8;
        v2 = p[Number_2 + row];
        v3 = p[Number_6 + row];
        v4 = (this.dctSqrt1d2 * (p[1 + row] - p[Number_7 + row]) + Number_128) >> Number_8;
        v7 = (this.dctSqrt1d2 * (p[1 + row] + p[Number_7 + row]) + Number_128) >> Number_8;
        v5 = p[Number_3 + row] << Number_4;
        v6 = p[Number_5 + row] << Number_4;


        t = (v0 - v1 + 1) >> 1;
        v0 = (v0 + v1 + 1) >> 1;
        v1 = t;
        t = (v2 * this.dctSin6 + v3 * this.dctCos6 + Number_128) >> Number_8;
        v2 = (v2 * this.dctCos6 - v3 * this.dctSin6 + Number_128) >> Number_8;
        v3 = t;
        t = (v4 - v6 + 1) >> 1;
        v4 = (v4 + v6 + 1) >> 1;
        v6 = t;
        t = (v7 + v5 + 1) >> 1;
        v5 = (v7 - v5 + 1) >> 1;
        v7 = t;


        t = (v0 - v3 + 1) >> 1;
        v0 = (v0 + v3 + 1) >> 1;
        v3 = t;
        t = (v1 - v2 + 1) >> 1;
        v1 = (v1 + v2 + 1) >> 1;
        v2 = t;
        t = (v4 * this.dctSin3 + v7 * this.dctCos3 + 2048) >> Number_12;
        v4 = (v4 * this.dctCos3 - v7 * this.dctSin3 + 2048) >> Number_12;
        v7 = t;
        t = (v5 * this.dctSin1 + v6 * this.dctCos1 + 2048) >> Number_12;
        v5 = (v5 * this.dctCos1 - v6 * this.dctSin1 + 2048) >> Number_12;
        v6 = t;

        p[0 + row] = v0 + v7;
        p[Number_7 + row] = v0 - v7;
        p[1 + row] = v1 + v6;
        p[Number_6 + row] = v1 - v6;
        p[ Number_2 + row] = v2 + v5;
        p[Number_5 + row] = v2 - v5;
        p[ Number_3 + row] = v3 + v4;
        p[Number_4 + row] = v3 - v4;
      }

      for (i = 0; i < Number_8; ++i) {
        let col:number = i;
        if (p[1 * Number_8 + col] == 0 && p[ Number_2 * Number_8 + col] == 0 && p[ Number_3 * Number_8 + col] == 0 &&
          p[ Number_4 * Number_8 + col] == 0 && p[Number_5 * Number_8 + col] == 0 && p[ Number_6 * Number_8 + col] == 0 &&
          p[ Number_7 * Number_8 + col] == 0) {
          t = (this.dctSqrt2 * dataIn[i+0] + 8192) >> Number_14;
          p[0 * Number_8 + col] = t;
          p[1 * Number_8 + col] = t;
          p[ Number_2 * Number_8 + col] = t;
          p[ Number_3 * Number_8 + col] = t;
          p[ Number_4 * Number_8 + col] = t;
          p[ Number_5 * Number_8 + col] = t;
          p[ Number_6 * Number_8 + col] = t;
          p[ Number_7 * Number_8 + col] = t;
          continue;
        }

        v0 = (this.dctSqrt2 * p[0 * Number_8 + col] + Number_2048) >> Number_12;
        v1 = (this.dctSqrt2 * p[ Number_4 * Number_8 + col] + Number_2048) >> Number_12;
        v2 = p[ Number_2 * Number_8 + col];
        v3 = p[ Number_6 * Number_8 + col];
        v4 = (this.dctSqrt1d2 * (p[1 * Number_8 + col] - p[ Number_7 * Number_8 + col]) + Number_2048) >> Number_12;
        v7 = (this.dctSqrt1d2 * (p[1 * Number_8 + col] + p[ Number_7 * Number_8 + col]) + Number_2048) >> Number_12;
        v5 = p[ Number_3 * Number_8 + col];
        v6 = p[ Number_5 * Number_8 + col];

        t = (v0 - v1 + 1) >> 1;
        v0 = (v0 + v1 + 1) >> 1;
        v1 = t;
        t = (v2 * this.dctSin6 + v3 * this.dctCos6 + 2048) >> Number_12;
        v2 = (v2 * this.dctCos6 - v3 * this.dctSin6 + 2048) >> Number_12;
        v3 = t;
        t = (v4 - v6 + 1) >> 1;
        v4 = (v4 + v6 + 1) >> 1;
        v6 = t;
        t = (v7 + v5 + 1) >> 1;
        v5 = (v7 - v5 + 1) >> 1;
        v7 = t;

        t = (v0 - v3 + 1) >> 1;
        v0 = (v0 + v3 + 1) >> 1;
        v3 = t;
        t = (v1 - v2 + 1) >> 1;
        v1 = (v1 + v2 + 1) >> 1;
        v2 = t;
        t = (v4 * this.dctSin3 + v7 * this.dctCos3 + 2048) >> Number_12;
        v4 = (v4 * this.dctCos3 - v7 * this.dctSin3 + 2048) >> Number_12;
        v7 = t;
        t = (v5 * this.dctSin1 + v6 * this.dctCos1 + 2048) >> Number_12;
        v5 = (v5 * this.dctCos1 - v6 * this.dctSin1 + 2048) >> Number_12;
        v6 = t;

        p[0 * Number_8 + col] = v0 + v7;
        p[ Number_7 * Number_8 + col] = v0 - v7;
        p[1 * Number_8 + col] = v1 + v6;
        p[ Number_6 * Number_8 + col] = v1 - v6;
        p[ Number_2 * Number_8 + col] = v2 + v5;
        p[ Number_5 * Number_8 + col] = v2 - v5;
        p[ Number_3 * Number_8 + col] = v3 + v4;
        p[ Number_4 * Number_8 + col] = v3 - v4;
      }

      for (i = 0; i < Number_64; ++i) {
        let sample:number = Number_128 + ((p[i] + Number_0x8) >> Number_4);
        copyDataOut[i] = sample < 0 ? 0 : sample > Number_0xFF ? Number_0xFF : sample;
      }
    }
    let i:number;
    let j:number;
    for (let blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
      let scanLine:number = blockRow << Number_3;
      for (i = 0; i < Number_8; i++){
        lines.push(new Uint8Array(samplesPerLine));
      }
      for (let blockCol = 0; blockCol < blocksPerLine; blockCol++) {
        quantizeAndInverse(component.blocks[blockRow][blockCol], r, R);
        let offset:number = 0;
        let sample:number = blockCol << Number_3;
        for (j = 0; j < Number_8; j++) {
          let line:Uint8Array = lines[scanLine + j];
          for (i = 0; i < Number_8; i++){
            line[sample + i] = r[offset];
            offset += 1
          }
        }
      }
    }
    return lines;
  }
  load(url: string): void {
    const xhr: XMLHttpRequest = PdfJS_window.xMLHttpRequest;
    xhr.open("GET", url);
    xhr.responseType = "arraybuffer";
    xhr.onload = () => {
      const data: Uint8Array = xhr.mozResponseArrayBuffer ?? xhr.mozResponse ?? xhr.responseArrayBuffer ?? xhr.response;
      this.parse(data);
    };
    xhr.send(null);
  }
  parse(data: Uint8Array) {
    let offset: number = 0;
    const length:number = data.length;

    let readUint16 = () => {
      let value:number = (data[offset] << Number_0x8) | data[offset + 1];
      offset += Number_2;
      return value;
    }
    let readDataBlock = (): Uint8Array => {
      let length:number = readUint16();
      let subArr:Uint8Array = data.subarray(offset, offset + length - Number_0x2);
      offset += subArr.length;
      return subArr;
    }
    let prepareComponents = (frame: JpegFrame): void => {
      let copyFrame: JpegFrame = frame;
      let maxH:number = 0;
      let maxV:number = 0;
      let component:FrameComponent;
      let componentId: string;
      const comps: Record<string, FrameComponent> = copyFrame.components as Record<string, FrameComponent>;
      for (componentId of Object.keys(comps)) {
        const currentComponent:FrameComponent = comps[componentId];
        if (currentComponent !== null && currentComponent !== undefined){
          component = currentComponent;
          if (maxH < component.h) {
            maxH = component.h;
          }
          if (maxV < component.v) {
            maxV = component.v;
          }
        }
      }
      let mcusPerLine:number = Math.ceil(copyFrame.samplesPerLine / Number_8 / maxH);
      let mcusPerColumn:number = Math.ceil(copyFrame.scanLines / Number_8 / maxV);
      for (let componentId of Array.from(Object.keys(comps))) {
        const currentComponent: FrameComponent = comps[componentId];
        if (currentComponent !== null && currentComponent !== undefined){
          component = currentComponent;
          const blocksPerLine: number = Math.ceil((Math.ceil((copyFrame.samplesPerLine ?? 0) / Number_8o) * component.h) / maxH);
          const blocksPerColumn: number = Math.ceil((Math.ceil((copyFrame.scanLines ?? 0) / Number_8o) * component.v) / maxV);
          const blocksPerLineForMcu: number = mcusPerLine * component.h;
          const blocksPerColumnForMcu: number = mcusPerColumn * component.v;
          let blocks: Int32Array[][] = new Array<Int32Array[]>();
          for (let i = 0; i < blocksPerColumnForMcu; i++) {
            let row: Int32Array[] = new Array<Int32Array>();
            for (let j = 0; j < blocksPerLineForMcu; j++) {
              row.push(new Int32Array(Number_64));
            }
            blocks.push(row);
          }
          component.blocksPerLine = blocksPerLine;
          component.blocksPerColumn = blocksPerColumn;
          component.blocks = blocks;
        }
      }
      copyFrame.maxH = maxH;
      copyFrame.maxV = maxV;
      copyFrame.mcusPerLine = mcusPerLine;
      copyFrame.mcusPerColumn = mcusPerColumn;
    }

    let jfif: Jfif = null;
    let adobe: Adobe = null;
    let frame: JpegFrame = {};
    let resetInterval:number;
    let quantizationTables: Int32Array[] = new Array<Int32Array>();
    let frames: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[] = new Array();
    let huffmanTablesAC: CodeObj[][] = new Array<CodeObj[]>();
    let huffmanTablesDC: CodeObj[][] = new Array<CodeObj[]>();
    let fileMarker:number = readUint16();
    if (fileMarker !== Number_0xFFD8) {
      error("SOI not found");
    }
    fileMarker = readUint16();
    while (fileMarker !== Number_0xFFD9) {
      switch (fileMarker) {
        case 0xFFE0:
        case 0xFFE1:
        case 0xFFE2:
        case 0xFFE3:
        case 0xFFE4:
        case 0xFFE5:
        case 0xFFE6:
        case 0xFFE7:
        case 0xFFE8:
        case 0xFFE9:
        case 0xFFEA:
        case 0xFFEB:
        case 0xFFEC:
        case 0xFFED:
        case 0xFFEE:
        case 0xFFEF:
        case 0xFFFE:
          let appData: Uint8Array = readDataBlock();
          if (fileMarker === Number_0xFFE0) {
            if (appData[0] === Number_0x4A && appData[1] === Number_0x46 && appData[Number_2] === Number_0x49 &&
              appData[Number_3] === Number_0x46 && appData[Number_4] === 0) {
              jfif = {
                version: { major: appData[Number_5], minor: appData[Number_6] },
                densityUnits: appData[Number_7],
                xDensity: (appData[Number_8] << Number_8) | appData[Number_9],
                yDensity: (appData[Number_10] << Number_8) | appData[Number_11],
                thumbWidth: appData[Number_12],
                thumbHeight: appData[Number_13],
                thumbData: appData.subarray(Number_14, Number_14 + Number_3 * appData[Number_12] * appData[Number_13])
              };
            }
          }
          if (fileMarker === Number_0xFFEE) {
            if (appData[0] === Number_0x41 && appData[1] === Number_0x64 && appData[Number_2] === Number_0x6F &&
              appData[Number_3] === Number_0x62 && appData[Number_4] === Number_0x65 && appData[Number_5] === 0) {
              adobe = {
                version: appData[Number_6],
                flags0: (appData[Number_7] << Number_8) | appData[Number_8],
                flags1: (appData[Number_9] << Number_8) | appData[Number_10],
                transformCode: appData[Number_11]
              };
            }
          }
          break;

        case 0xFFDB:
          let quantizationTableCount:number = Math.floor((readUint16() - Number_2) / Number_65);
          for (let i = 0; i < quantizationTableCount; i++) {
            let quantizationTableSpec: number = data[offset];
            offset += 1;
            let tableData:Int32Array = new Int32Array(Number_64);
            if ((quantizationTableSpec >> Number_4) === 0) {
              for (let j = 0; j < Number_64; j++) {
                let z:number = this.dctZigZag[j];
                tableData[z] = data[offset];
                offset += 1;
              }
            } else if ((quantizationTableSpec >> Number_4) === 1) {
              for (let j = 0; j < Number_64; j++) {
                let z:number = this.dctZigZag[j];
                tableData[z] = readUint16();
              }
            } else{
              error("DQT: invalid table spec");
            }
            quantizationTables[quantizationTableSpec & Number_15] = tableData;
          }
          break;
        case 0xFFC0:
        case 0xFFC2:
          readUint16();
          frame.progressive = (fileMarker === Number_0xFFC2);
          frame.precision = data[offset];
          offset += 1;
          frame.scanLines = readUint16();
          frame.samplesPerLine = readUint16();
          frame.components = {};
          frame.componentsOrder = new Array<number>();
          let componentsCount:number = data[offset];
          offset += 1;
          let componentId:number;
          for (let i = 0; i < componentsCount; i++) {
            componentId = data[offset];
            const h: number = data[offset + 1] >> Number_4;
            const v: number = data[offset + 1] & Number_15;
            const qId: number = data[offset + Number_2];
            const component: FrameComponent = { h: h, v: v, quantizationTable: quantizationTables[qId]};
            let componentsDictionary:{ [componentId: string]: FrameComponent } = frame.components;
            if (componentsDictionary !== null && componentsDictionary !== undefined){
              componentsDictionary[componentId] = component
              frame.components = componentsDictionary
            }
            frame.componentsOrder.push(componentId);
            offset += Number_3;
          }
          prepareComponents(frame);
          frames.push(frame);
          break;
        case 0xFFC4:
          let huffmanLength: number = readUint16();
          let i:number = Number_2
          while (i < huffmanLength) {
            let huffmanTableSpec: number = data[offset];
            offset += 1;
            let codeLengths:Uint8Array = new Uint8Array(Number_16);
            let codeLengthSum:number = 0;
            for (let j = 0; j < Number_16; j++){
              let codeLength: number = data[offset]
              codeLengths[j] = codeLength
              codeLengthSum += codeLength
              offset += 1
            }
            let huffmanValues:Uint8Array = new Uint8Array(codeLengthSum);
            for (let j = 0; j < codeLengthSum; j++) {
              huffmanValues[j] = data[offset];
              offset += 1;
            }
            i += 17 + codeLengthSum;
            if ((huffmanTableSpec >> Number_4) === 0) {
              let index: number = huffmanTableSpec & Number_15;
              huffmanTablesDC[index] = this.buildHuffmanTable(codeLengths, huffmanValues);
            } else {
              let index: number = huffmanTableSpec & Number_15;
              huffmanTablesAC[index] = this.buildHuffmanTable(codeLengths, huffmanValues);
            }
          }
          break;

        case 0xFFDD:
          readUint16();
          resetInterval = readUint16();
          break;
        case 0xFFDA:
          readUint16();
          let selectorsCount:number = data[offset];
          offset += 1;
          let components: FrameComponent[] = new Array<FrameComponent>();
          let component: FrameComponent;
          for (let i = 0; i < selectorsCount; i++) {
            let componentIndex: number = data[offset]
            offset += 1;
            let comps: FrameComponent[] = frame.components as FrameComponent[];
            component = comps[componentIndex]
            let tableSpec: number = data[offset]
            offset += 1
            component.huffmanTableDC = huffmanTablesDC[tableSpec >> Number_4]
            component.huffmanTableAC = huffmanTablesAC[tableSpec & Number_15]
            components.push(component)
          }
          const spectralStart: number = data[offset];
          offset += 1;
          const spectralEnd: number = data[offset];
          offset += 1;
          const successiveApproximation: number = data[offset];
          offset += 1;
          const processed: number = this.decodeScan(data, offset,
            frame, components, resetInterval,
            spectralStart, spectralEnd,
            successiveApproximation >> Number_4, successiveApproximation & Number_15);
          offset += processed;
          break;

        default:
          error('unknown JPEG marker');
      }
      fileMarker = readUint16();
    }

    if (frames.length !== 1){
      error("only single frame JPEGs supported");
    }
    this.width = frame.samplesPerLine;
    this.height = frame.scanLines;
    this.jfif = jfif;
    this.adobe = adobe;
    this.components =  new Array<ImageComponent>();
    for (let i = 0; i < frame.componentsOrder.length; i++) {
      let compont:FrameComponent[] = frame.components as FrameComponent[];
      let component: FrameComponent = compont[(frame.componentsOrder[i])]
      let imgCom:ImageComponent = { lines: this.buildComponentData(frame, component), scaleX: component.h / frame.maxH, scaleY: component.v / frame.maxV
      }
      this.components.push(imgCom)
    }

  }
  getData(width:number, height:number): Uint8Array {
    let clampTo8bit = (a: number): number => {
      return a < 0 ? 0 : a > Number_255 ? Number_255 : a;
    }

    const scaleX:number= this.width / width;
    const scaleY:number = this.height / height;
    let component1:ImageComponent;
    let component2:ImageComponent;
    let component3:ImageComponent;
    let component4:ImageComponent;
    let component1Line:Uint8Array;
    let component2Line:Uint8Array;
    let component3Line:Uint8Array;
    let component4Line:Uint8Array;
    let offset:number = 0;
    let Y:number;
    let Cb:number;
    let Cr:number;
    let K:number;
    let C:number;
    let M:number;
    let Ye:number;
    let R:number;
    let G:number;
    let B:number;
    let colorTransform: boolean;
    const dataLength:number = width * height * this.components.length;
    let data:Uint8Array = new Uint8Array(dataLength);

    switch (this.components.length) {
      case 1:
        component1 = this.components[0];
        for (let y = 0; y < height; y++) {
          component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)];
          for (let x = 0; x < width; x++) {
            Y = component1Line[0 | (x * component1.scaleX * scaleX)];
            data[offset] = Y;
            offset += 1;
          }
        }
        break;
      case Number_3:
        colorTransform = true;
        if (this.adobe !== null && this.adobe.transformCode !== 0){
          colorTransform = true;
        }
        else if (this.colorTransform !== null){
          colorTransform = this.colorTransform;
        }
        component1 = this.components[0];
        component2 = this.components[1];
        component3 = this.components[Number_2];

        for (let y = 0; y < height; y++) {

          component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)];
          component2Line = component2.lines[0 | (y * component2.scaleY * scaleY)];
          component3Line = component3.lines[0 | (y * component3.scaleY * scaleY)];
          for (let x = 0; x < width; x++) {
            if (!colorTransform) {
              R = component1Line[0 | (x * component1.scaleX * scaleX)];
              G = component2Line[0 | (x * component2.scaleX * scaleX)];
              B = component3Line[0 | (x * component3.scaleX * scaleX)];
            } else {
              Y = component1Line[0 | (x * component1.scaleX * scaleX)];
              Cb = component2Line[0 | (x * component2.scaleX * scaleX)];
              Cr = component3Line[0 | (x * component3.scaleX * scaleX)];

              R = clampTo8bit(Y + 1.402 * (Cr - Number_128));
              G = clampTo8bit(Y - 0.3441363 * (Cb - Number_128) - 0.71413636 * (Cr - Number_128));
              B = clampTo8bit(Y + 1.772 * (Cb - Number_128));
            }

            data[offset] = R;
            offset += 1;
            data[offset] = G;
            offset += 1;
            data[offset] = B;
            offset += 1;
          }
        }
        break;
      case Number_4:
        if (this.adobe === null){
          error('Unsupported color mode (4 components)');
        }
        colorTransform = false;
        if (this.adobe !== null && this.adobe.transformCode !== 0){
          colorTransform = true;
        }
        else if (this.colorTransform !== null){
          colorTransform = this.colorTransform!;
        }

        component1 = this.components[0];
        component2 = this.components[1];
        component3 = this.components[Number_2];
        component4 = this.components[Number_3];

        for (let y = 0; y < height; y++) {
          component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)];
          component2Line = component2.lines[0 | (y * component2.scaleY * scaleY)];
          component3Line = component3.lines[0 | (y * component3.scaleY * scaleY)];
          component4Line = component4.lines[0 | (y * component4.scaleY * scaleY)];

          for (let x = 0; x < width; x++) {
            if (!colorTransform) {
              C = component1Line[0 | (x * component1.scaleX * scaleX)];
              M = component2Line[0 | (x * component2.scaleX * scaleX)];
              Ye = component3Line[0 | (x * component3.scaleX * scaleX)];
              K = component4Line[0 | (x * component4.scaleX * scaleX)];
            } else {
              Y = component1Line[0 | (x * component1.scaleX * scaleX)];
              Cb = component2Line[0 | (x * component2.scaleX * scaleX)];
              Cr = component3Line[0 | (x * component3.scaleX * scaleX)];
              K = component4Line[0 | (x * component4.scaleX * scaleX)];

              C = Number_255 - clampTo8bit(Y + 1.402 * (Cr - Number_128));
              M = Number_255 - clampTo8bit(Y - 0.3441363 * (Cb - Number_128) - 0.71413636 * (Cr - Number_128));
              Ye = Number_255 - clampTo8bit(Y + 1.772 * (Cb - Number_128));
            }

            data[offset] = C;
            offset += 1;
            data[offset] = M;
            offset += 1;
            data[offset] = Ye;
            offset += 1;
            data[offset] = K;
            offset += 1;
          }
        }
        break;
      default:
        error('Unsupported color mode');
    }
    return data;
  }

  clampTo8bit(a: number): number {
    return a < 0 ? 0 : a > Number_255 ? Number_255 : a;
  }

  copyToImageData(imageData:ImageData) {
    let width:number = imageData.width;
    let height:number = imageData.height;
    let imageDataArray:Uint8Array = imageData.data;
    let data:Uint8Array = this.getData(width, height);
    let i:number = 0;
    let j:number = 0;
    let Y: number;
    let K: number;
    let C: number;
    let M: number;
    let R: number;
    let G: number;
    let B: number;
    switch (this.components.length) {
      case 1:
        for (let y = 0; y < height; y++) {
          for (let x = 0; x < width; x++) {
            Y = data[i];
            i += 1;
            imageDataArray[j] = Y;
            j += 1;
            imageDataArray[j] = Y;
            j += 1;
            imageDataArray[j] = Y;
            j += 1;
            imageDataArray[j] = Number_255;
            j += 1;
          }
        }
        break;
      case Number_3:
        for (let y = 0; y < height; y++) {
          for (let x = 0; x < width; x++) {
            R = data[i];
            i += 1;
            G = data[i];
            i += 1;
            B = data[i];
            i += 1;
            imageDataArray[j] = R;
            j += 1;
            imageDataArray[j] = G;
            j += 1;
            imageDataArray[j] = B;
            j += 1;
            imageDataArray[j] = Number_255;
            j += 1;
          }
        }
        break;
      case Number_4:
        for (let y = 0; y < height; y++) {
          for (let x = 0; x < width; x++) {
            C = data[i];
            i += 1;
            M = data[i];
            i += 1;
            Y = data[i];
            i += 1;
            K = data[i];
            i += 1;

            R = Number_255 - this.clampTo8bit(C * (1 - K / Number_255) + K);
            G = Number_255 - this.clampTo8bit(M * (1 - K / Number_255) + K);
            B = Number_255 - this.clampTo8bit(Y * (1 - K / Number_255) + K);

            imageDataArray[j] = R;
            j += 1;
            imageDataArray[j] = G;
            j += 1;
            imageDataArray[j] = B;
            j += 1;
            imageDataArray[j] = Number_255;
            j += 1;
          }
        }
        break;
      default:
        error('Unsupported color mode');
    }
  }
}

interface PrecinctsSize {
  PPx?: number;
  PPy?: number;
  xcb?: number;
  ycb?: number;
}

interface CodingStyleParameters {
  entropyCoderWithCustomPrecincts?: boolean;
  sopMarkerUsed?: boolean;
  ephMarkerUsed?: boolean;
  progressionOrder?: number;
  layersCount?: number;
  multipleComponentTransform?: number;
  decompositionLevelsCount?: number;
  xcb?: number;
  ycb?: number;
  selectiveArithmeticCodingBypass?: boolean;
  resetContextProbabilities?: boolean;
  terminationOnEachCodingPass?: boolean;
  verticalyStripe?: boolean;
  predictableTermination?: boolean;
  segmentationSymbolUsed?: boolean;
  transformation?: number;
  precinctsSizes?: PrecinctsSize[];

}

interface QuantizationParameters {
  epsilon?: number;
  mu?: number;
  noQuantization?: boolean;
  SPqcds?: Spqcd[];
  scalarExpounded?: boolean;
  guardBits?: number;
}

interface TileComponents {
  tcx0?: number;
  tcy0?: number;
  tcx1?: number;
  tcy1?: number;
  width?: number;
  height?: number;
  resolutions?: Resolutions[];
  codingStyleParameters?: CodingStyleParameters;
  quantizationParameters?: QuantizationParameters;
  subbands?: Array<Subband>;
}

interface Tile {
  tx0?: number;
  ty0?: number;
  tx1?: number;
  ty1?: number;
  tcx0?: number;
  tcy0?: number;
  tcx1?: number;
  tcy1?: number;
  width?: number;
  height?: number;
  components?: TileComponents[];
  index?: number;
  length?: number;
  dataEnd?: number;
  partIndex?: number;
  partsCount?: number;
  COD?: CodingStyleParameters;
  COC?: Record<string, Record<string, object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>>[] | null;
  QCD?: Record<string, object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>[] | null;
  QCC?: Record<string, Record<string, object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>>[] | null;
  codingStyleDefaultParameters?: CodingStyleParameters;
  packetsIterator?: PacketsIterator;
}

class PacketsIterator {
  layerNumber?: number;
  codeblocks?: Codeblock[];

  constructor(layerNumber?:number, codeblocks?:Codeblock[]) {
    this.layerNumber = layerNumber;
    this.codeblocks = codeblocks;
  }

  nextPacket(): PacketsIterator | null {
    this.layerNumber += 1;
    if (this.layerNumber < this.codeblocks.length) {
      return new PacketsIterator(this.layerNumber, this.codeblocks);
    }
    return null;
  }
}
interface Resolutions {
  trx0?: number;
  try0?: number;
  trx1?: number;
  try1?: number;
  subbands?: Array<Subband>;
  precinctParameters?: PrecinctParameters;

}

interface Subband {
  trx0?: number;
  try0?: number;
  trx1?: number;
  try1?: number;
  type?: string;
  tbx0?: number;
  tby0?: number;
  tbx1?: number;
  tby1?: number;
  resolution?: Resolutions;
  codeblockParameters?: CodeblockParameter;
  codeblocks?: Codeblock[];
  precincts?: Precinct[];
}

interface Precinct {
  cbxMin: number;
  cbyMin: number;
  cbxMax: number;
  cbyMax: number;
  inclusionTree?: InclusionTree;
  zeroBitPlanesTree?: TagTree;
}

interface CodeblockData {
  data: Uint8Array;
  start: number;
  end: number;
  codingpasses: number;
}

interface Codeblock {
  cbx: number;
  cby: number;
  tbx0: number;
  tby0: number;
  tbx1: number;
  tby1: number;
  precinctNumber?: number;
  precinct?: Precinct;
  subbandType?: string;
  Lblock?: number;
  included?: boolean;
  zeroBitPlanes?: number;
  data?: CodeblockData[];
}

interface CodeblockParameter {
  codeblockWidth: number;
  codeblockHeight: number;
  numcodeblockwide: number;
  numcodeblockhigh: number;
}

interface PrecinctParameters {
  precinctXOffset: number;
  precinctWidth: number;
  precinctYOffset: number;
  precinctHeight: number;
  numprecinctswide: number;
  numprecinctshigh?: number;
  numprecincts?: number;
}

interface ImageSize {
  Xsiz: number;
  Ysiz: number;
  XOsiz: number;
  YOsiz: number;
  XTsiz: number;
  YTsiz: number;
  XTOsiz: number;
  YTOsiz: number;
  Csiz: number;
}

interface Components {
  precision?: number;
  isSigned?: boolean;
  XRsiz?: number;
  YRsiz?: number;
  x0?: number;
  x1?: number;
  y0?: number;
  y1?: number;
  width?: number;
  height?: number;
  quantizationParameters?: QuantizationParameters[];
  codingStyleParameters?: CodingStyleParameters;
}

interface CurrentTile {
  index?: number;
  length?: number;
  dataEnd?: number;
  partIndex?: number;
  partsCount?: number;
  COD?: CodingStyleParameters;
  COC?: CodingStyleParameters[];
  QCD?: QuantizationParameters;
  QCC?: QuantizationParameters[];
}

interface Spqcd {
  epsilon?: number;
  mu?: number;
}

class ImageContext extends PDFContext {
  mainHeader: boolean;
  SIZ: ImageSize;
  components: Components[];
  COD: CodingStyleParameters;
  COC: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[];
  QCD: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name;
  QCC: (object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name | Map<string, Map<string, string> | Metadata>)[];
  currentTile: CurrentTile;
  tiles: Tile[];
}

interface ImageCod {
  entropyCoderWithCustomPrecincts?: boolean;
  sopMarkerUsed?: boolean;
  ephMarkerUsed?: boolean;
  progressionOrder?: number;
  layersCount?: number;
  multipleComponentTransform?: number;
  decompositionLevelsCount?: number;
  xcb?: number;
  ycb?: number;
  selectiveArithmeticCodingBypass?: boolean;
  resetContextProbabilities?: boolean;
  terminationOnEachCodingPass?: boolean;
  verticalyStripe?: boolean;
  predictableTermination?: boolean;
  segmentationSymbolUsed?: boolean;
  transformation?: number;
  precinctsSizes?: PrecinctsSize[];
}

interface TransformResult {
  left?: number;
  top?: number;
  width?: number;
  height?: number;
  items?: Uint8Array;
  index?: number;
}

class PacketItem {
  codeblock: Codeblock;
  codingpasses: number;
  dataLength: number;
}

interface SignContextLabel {
  contextLabel: number;
  xorBit: number;
}

class BitModel {
  width: number;
  height: number;
  contextLabelTable: Uint8Array;
  neighborsSignificance: Uint8Array;
  coefficentsSign: Uint8Array;
  coefficentsMagnitude: Uint32Array;
  processingFlags: Uint8Array;
  bitsDecoded: Uint8Array;
  decoder: ArithmeticDecoder;

  uniformContext: [index: number, mps: number];
  runLengthContext: [index: number, mps: number];
  contexts: [index: number, mps: number][];

  contextLabels: Uint8Array;
  significanceState: number;

  LLAndLHContextsLabel = new Uint8Array([
    0, 5, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 1, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8
  ]);
  HLContextLabel = new Uint8Array([
    0, 3, 4, 0, 5, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 1, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8
  ]);
  HHContextLabel = new Uint8Array([
    0, 1, 2, 0, 1, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 0, 3, 4, 5, 0, 4, 5, 5, 0, 5, 5, 5, 0, 0, 0, 0, 0, 6, 7, 7, 0, 7, 7, 7, 0, 7, 7, 7, 0, 0, 0, 0, 0, 8, 8, 8, 0, 8, 8, 8, 0, 8, 8, 8, 0, 0, 0, 0, 0, 8, 8, 8, 0, 8, 8, 8, 0, 8, 8, 8
  ]);

  signContextLabels: SignContextLabel[] = [
    { contextLabel: 13, xorBit: 0 }, { contextLabel: 12, xorBit: 0 }, { contextLabel: 11, xorBit: 0 }, { contextLabel: 10, xorBit: 0 }, { contextLabel: 9, xorBit: 0 }, { contextLabel: 10, xorBit: 1 }, { contextLabel: 11, xorBit: 1 }, { contextLabel: 12, xorBit: 1 }, { contextLabel: 13, xorBit: 1 }
  ];

  public calcSignContribution(significance0: boolean, sign0: boolean, significance1: boolean, sign1: boolean): number {
    if (significance1) {
      if (!sign1) {
        return significance0 ? (!sign0 ? 1 : 0) : 1;
      } else {
        return significance0 ? (!sign0 ? 0 : -1) : -1;
      }
    } else {
      return significance0 ? (!sign0 ? 1 : -1) : 0;
    }
  }

  constructor(width: number, height: number, subband: string, zeroBitPlanes: number) {
    this.width = width;
    this.height = height;
    this.contextLabelTable = (subband === 'HH') ? this.HHContextLabel : (subband === 'HL') ? this.HLContextLabel : this.LLAndLHContextsLabel;

    this.decoder = new ArithmeticDecoder(new Uint8Array(),0,0);
    this.significanceState = 0;
    this.contextLabels = new Uint8Array();

    const coefficientCount: number = width * height;
    this.neighborsSignificance = new Uint8Array(coefficientCount);
    this.coefficentsSign = new Uint8Array(coefficientCount);
    this.coefficentsMagnitude = new Uint32Array(coefficientCount);
    this.processingFlags = new Uint8Array(coefficientCount);

    let bitsDecoded:Uint8Array = new Uint8Array(coefficientCount);
    for (let i = 0 ; i < coefficientCount; i++) {
      bitsDecoded[i] = zeroBitPlanes;
    }
    this.bitsDecoded = bitsDecoded;

    this.uniformContext = [46, 0];
    this.runLengthContext = [3, 0];
    this.contexts = new Array<[index: number, mps: number]>();
    this.contexts.push([ Number_4, 0]);
    for (let i = 1; i <= Number_16; i++) {
      this.contexts.push( [0, 0]);
    }
    this.reset();
  }

  setDecoder(decoder: ArithmeticDecoder) {
    this.decoder = decoder;
  }

  reset() {
  }

  setNeighborsSignificance(row: number, column: number) {
    let neighborsSignificance:Uint8Array = this.neighborsSignificance;
    const width:number = this.width;
    const height:number = this.height;
    const index:number = row * width + column;
    if (row > 0) {
      if (column > 0) {
        neighborsSignificance[index - width - 1] += Number_0x10;
      }
      if (column + 1 < width) {
        neighborsSignificance[index - width + 1] += Number_0x10;
      }
      neighborsSignificance[index - width] += Number_0x04;
    }
    if (row + 1 < height) {
      if (column > 0) {
        neighborsSignificance[index + width - 1] += Number_0x10;
      }
      if (column + 1 < width) {
        neighborsSignificance[index + width + 1] += Number_0x10;
      }
      neighborsSignificance[index + width] += Number_0x04;
    }
    if (column > 0) {
      neighborsSignificance[index - 1] += Number_0x01;
    }
    if (column + 1 < width) {
      neighborsSignificance[index + 1] += Number_0x01;
    }
    neighborsSignificance[index] |= Number_0x80;
  }

  runSignificancePropogationPass() {
    const decoder:ArithmeticDecoder = this.decoder;
    const width:number = this.width;
    const height:number = this.height;
    let coefficentsMagnitude:Uint32Array = this.coefficentsMagnitude;
    let coefficentsSign:Uint8Array = this.coefficentsSign;
    let contextLabels:Uint8Array = this.contextLabels;
    const neighborsSignificance:Uint8Array = this.neighborsSignificance;
    let processingFlags:Uint8Array = this.processingFlags;
    const contexts:[index: number, mps: number][] = this.contexts;
    const labels:Uint8Array = this.contextLabelTable;
    const bitsDecoded:Uint8Array = this.bitsDecoded;
    const processedInverseMask:number = ~1;
    const processedMask:number = 1;
    const firstMagnitudeBitMask:number = Number_2;
    for (let q = 0; q < width * height; q++) {
      processingFlags[q] &= processedInverseMask;
    }
    let i0:number = 0
    while (i0 < height) {
      let j:number = 0;
      while (j < width ) {
        let index:number = i0 * width + j;
        let i1:number = 0;
        while (i1 < Number_4 ) {
          const i:number = i0 + i1;
          if (i >= height) {
            break;
          }
          if (coefficentsMagnitude[index] !== 0 || (neighborsSignificance[index] == 0)) {
            i1 += 1;
            index += width;
            continue;
          }
          const contextLabel:number = labels[neighborsSignificance[index]];
          const cx:[index: number, mps: number] = contexts[contextLabel];
          const decision:boolean = (decoder.readBit(cx) !== 0);
          if (decision) {
            const sign:number = this.decodeSignBit(i, j);
            coefficentsSign[index] = sign;
            coefficentsMagnitude[index] = 1;
            this.setNeighborsSignificance(i, j);
            processingFlags[index] |= firstMagnitudeBitMask;
          }
          bitsDecoded[index] += 1;
          processingFlags[index] |= processedMask;
          i1 += 1;
          index += width;
        }
        j += 1;
      }
      i0 += Number_4;
    }
  }
  decodeSignBit(row: number, column: number) {
    const width:number = this.width;
    const height:number = this.height;
    const index:number = row * width + column;
    const coefficentsMagnitude:Uint32Array = this.coefficentsMagnitude;
    const coefficentsSign:Uint8Array = this.coefficentsSign;

    const horizontalContribution:number = this.calcSignContribution(
      column > 0 && coefficentsMagnitude[index - 1] !== 0,
      (coefficentsSign[index - 1] !== 0),
      column + 1 < width && coefficentsMagnitude[index + 1] !== 0,
      (coefficentsSign[index + 1] !== 0)
    );

    const verticalContribution = this.calcSignContribution(
      row > 0 && coefficentsMagnitude[index - width] !== 0,
      (coefficentsSign[index - width] !== 0),
      row + 1 < height && coefficentsMagnitude[index + width] !== 0,
      (coefficentsSign[index + width] !== 0)
    );

    const contextLabelAndXor:SignContextLabel = this.signContextLabels[
      3 * (1 - horizontalContribution) + (1 - verticalContribution)
    ];

    const contextLabel:number = contextLabelAndXor.contextLabel;
    const cx:[index: number, mps: number] = this.contexts[contextLabel];
    const decoded:number = this.decoder.readBit(cx);

    return decoded ^ contextLabelAndXor.xorBit;
  }
  runMagnitudeRefinementPass() {
    const decoder: ArithmeticDecoder = this.decoder;
    const width:number = this.width;
    const height:number = this.height;
    let coefficentsMagnitude:Uint32Array = this.coefficentsMagnitude;
    const neighborsSignificance:Uint8Array = this.neighborsSignificance;
    const contexts:[index: number, mps: number][] = this.contexts;
    let bitsDecoded:Uint8Array = this.bitsDecoded;
    let processingFlags:Uint8Array = this.processingFlags;
    const processedMask:number = 1;
    const firstMagnitudeBitMask:number = Number_2;
    let i0:number = 0;
    while ( i0 < height ) {
      let j:number = 0;
      while ( j < width ) {
        let i1:number = 0;
        while ( i1 < Number_4 ) {
          const i:number = i0 + i1;
          if (i >= height) {
            break;
          }
          const index:number = i * width + j;
          if (coefficentsMagnitude[index] === 0 || (processingFlags[index] & processedMask) !== 0) {
            i1 += 1;
            continue;
          }

          let contextLabel:number = Number_16;
          if ((processingFlags[index] & firstMagnitudeBitMask) !== 0) {
            processingFlags[i * width + j] ^= firstMagnitudeBitMask;
            const significance: number = neighborsSignificance[index];
            const sumOfSignificance: number = (significance & Number_3) + ((significance >> Number_2) & Number_3) + ((significance >> Number_4) & Number_7);
            contextLabel = sumOfSignificance >= 1 ? Number_15 : Number_14;
          }

          const cx:[index: number, mps: number] = contexts[contextLabel];
          const bit:number = decoder.readBit(cx);
          coefficentsMagnitude[index] = (coefficentsMagnitude[index] << 1) | bit;
          bitsDecoded[index] += 1;
          processingFlags[index] |= processedMask;
          i1 += 1;
        }
        j += 1;
      }
      i0 += Number_4;
    }
  }

  runCleanupPass() {
    const decoder:ArithmeticDecoder = this.decoder;
    const width:number = this.width;
    const height:number = this.height;
    const neighborsSignificance:Uint8Array = this.neighborsSignificance;
    const significanceState:number = this.significanceState;
    let coefficentsMagnitude:Uint32Array = this.coefficentsMagnitude;
    let coefficentsSign:Uint8Array = this.coefficentsSign;
    const contexts:[index: number, mps: number][] = this.contexts;
    const labels:Uint8Array = this.contextLabelTable;
    let bitsDecoded:Uint8Array = this.bitsDecoded;
    let processingFlags: Uint8Array = this.processingFlags;
    const processedMask:number = 1;
    const firstMagnitudeBitMask:number = Number_2;
    const oneRowDown:number = width;
    const twoRowsDown:number = width * Number_2;
    const threeRowsDown:number = width * Number_3;
    let i0:number = 0;
    while (i0 < height) {
      let j:number = 0;
      while ( j < width ) {
        const index0:number = i0 * width + j;
        const allEmpty:boolean = i0 + Number_3 < height &&
          processingFlags[index0] === 0 &&
          processingFlags[index0 + oneRowDown] === 0 &&
          processingFlags[index0 + twoRowsDown] === 0 &&
          processingFlags[index0 + threeRowsDown] === 0 &&
          neighborsSignificance[index0] === 0 &&
          neighborsSignificance[index0 + oneRowDown] === 0 &&
          neighborsSignificance[index0 + twoRowsDown] === 0 &&
          neighborsSignificance[index0 + threeRowsDown] === 0;
        let i1:number = 0;
        let index:number = index0;
        let cx:[index: number, mps: number];
        let i:number;
        if (allEmpty) {
          cx = this.runLengthContext;
          const hasSignificantCoefficent:boolean = (decoder.readBit(cx) !== 0);
          if (!hasSignificantCoefficent) {
            bitsDecoded[index0] += 1;
            bitsDecoded[index0 + oneRowDown] += 1;
            bitsDecoded[index0 + twoRowsDown] += 1;
            bitsDecoded[index0 + threeRowsDown] += 1;
            j += 1;
            continue;
          }
          cx = this.uniformContext;
          i1 = (decoder.readBit(cx) << 1) | decoder.readBit(cx);
          i = i0 + i1;
          index += i1 * width;
          const sign:number = this.decodeSignBit(i, j);
          coefficentsSign[index] = sign;
          coefficentsMagnitude[index] = 1;
          this.setNeighborsSignificance(i, j);
          processingFlags[index] |= firstMagnitudeBitMask;
          index = index0;
          for (let i2: number = i0; i2 <= i; i2++) {
            bitsDecoded[index] += 1;
            index += width;
          }
          i1 += 1;
        }
        while ( i1 < Number_4 ) {
          i = i0 + i1;
          if (i >= height) {
            break;
          }
          if (coefficentsMagnitude[index] != 0 || (processingFlags[index] & processedMask) !== 0) {
            i1 += 1;
            index += width;
            continue;
          }
          const contextLabel:number = labels[neighborsSignificance[index]];
          cx = contexts[contextLabel];
          const decision:number = decoder.readBit(cx);
          if (decision === 1) {
            const sign:number = this.decodeSignBit(i, j);
            coefficentsSign[index] = sign;
            coefficentsMagnitude[index] = 1;
            this.setNeighborsSignificance(i, j);
            processingFlags[index] |= firstMagnitudeBitMask;
          }
          bitsDecoded[index] += 1;
          i1 += 1;
          index += width
        }
        j += 1;
      }
      i0 += Number_4;
    }
  }
}

class JpxImage {
  private SubbandsGainLog2: Record<string, number> = {
    LL: 0,
    LH: 1,
    HL: 1,
    HH: 2
  }
  tiles: TransformResult[][];
  width: number = 0;
  height: number = 0;
  componentsCount: number = 0;
  private failOnCorruptedImage: boolean = false;

  constructor() {
    this.failOnCorruptedImage = false;
  }
  onload(url?: string): void {
  }
  load(url: string): void {
    const xhr: XMLHttpRequest = PdfJS_window.xMLHttpRequest;
    xhr.open("GET", url);
    xhr.responseType = "arraybuffer";
    xhr.onload = () => {
      if (xhr.readyState === Number_4) {
        if (xhr.status === xhr.expected) {
          const data: object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name = xhr.mozResponseArrayBuffer || xhr.mozResponse || xhr.responseArrayBuffer || xhr.response;
          this.parse(data as Uint8Array);
          this.onload(null);
        }
      }
    };
    xhr.send(null);
  }

  parse(data: Uint8Array): void {
    let readUint = (data: Uint8Array, offset: number, bytes: number): number => {
      let n:number = 0;
      for (let i = 0; i < bytes; i++) {
        n = n * Number_256 + (data[offset + i] & Number_0xFF);
      }
      return n;
    }

    let position:number = 0;
    const length:number = data.length;
    while (position < length) {
      let headerSize:number = Number_8;
      let lbox:number = readUint(data, position, Number_4);
      const tbox:number = readUint(data, position + Number_4, Number_4);
      position += headerSize;
      if (lbox === 1) {
        lbox = readUint(data, position, Number_8);
        position += Number_8;
        headerSize += Number_8;
      }
      if (lbox === 0) {
        lbox = length - position + headerSize;
      }
      if (lbox < headerSize) {
        error('JPX error: Invalid box field size');
      }
      const dataLength:number = lbox - headerSize;
      let jumpDataLength:boolean = true;
      switch (tbox) {
        case 0x6A501A1A:
          break;
        case 0x6A703268:
          jumpDataLength = false;
          break;
        case 0x636F6C72:
          break;
        case 0x6A703263:
          this.parseCodestream(data, position, position + dataLength);
          break;
        default:
          break;
      }
      if (jumpDataLength) {
        position += dataLength;
      }
    }
  }

  readUint32(data: Uint8Array, offset: number): number {
    return (data[offset] << Number_24) |
      (data[offset + 1] << Number_16) |
      (data[offset + Number_2] << Number_8) |
    data[offset + Number_3];
  }

  private readUint16(data:Uint8Array, offset:number) {
    return (data[offset] << Number_8) | data[offset + 1];
  }

  parseCodestream(data: Uint8Array, start: number, end: number): void {
    let context: ImageContext = new ImageContext();
    try {
      let position: number = start;
      while (position < end) {
        const code:number = this.readUint16(data, position);
        position += Number_2;

        let length:number = 0;
        let j: number;
        switch (code) {
          case 0xFF4F:
            context.mainHeader = true;
            break;
          case 0xFFD9:
            break;
          case 0xFF51: {
            length = this.readUint16(data, position);
            let siz: ImageSize = context.SIZ;
            siz.Xsiz = this.readUint32(data, position + Number_4);
            siz.Ysiz = this.readUint32(data, position + Number_8);
            siz.XOsiz = this.readUint32(data, position + Number_12);
            siz.YOsiz = this.readUint32(data, position + Number_16);
            siz.XTsiz = this.readUint32(data, position + Number_20);
            siz.YTsiz = this.readUint32(data, position + Number_24);
            siz.XTOsiz = this.readUint32(data, position + Number_28);
            siz.YTOsiz = this.readUint32(data, position + Number_32);
            const componentsCount:number = this.readUint16(data, position + Number_36);
            siz.Csiz = componentsCount;
            let components: Components[] = new Array<Components>();
            j = position + Number_38;
            for (let i = 0; i < componentsCount; i++) {
              const component: Components = {
                precision: (data[j] & Number_0x7F) + 1,
                isSigned: (data[j] & Number_0x80) !== 0,
                XRsiz: data[j + 1],
                YRsiz: data[j + 1]
              };
              this.calculateComponentDimensions(component, siz);
              components.push(component);
            }
            context.SIZ = siz;
            context.components = components;
            this.calculateTileGrids(context, components);
            context.QCC = new Array<object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>();
            context.COC = new Array<object | boolean | string | number | object[] | boolean[] | string[] | number[] | null | Dict | Stream | Name>();
          }
            break;
          case 0xFF5C: {
            length = this.readUint16(data, position);
            let qcd: QuantizationParameters = {};
            j = position + Number_2;
            let sqcd: number = data[j];
            j += 1;
            let spqcdSize:number;
            let scalarExpounded: boolean;
            switch (sqcd & Number_0x1F) {
              case 0:
                spqcdSize = Number_8;
                scalarExpounded = true;
                break;
              case 1:
                spqcdSize = Number_16;
                scalarExpounded = false;
                break;
              case Number_2:
                spqcdSize = Number_16;
                scalarExpounded = true;
                break;
              default:
                error(`Invalid SQcd value  ${sqcd}`);
            }
            qcd.noQuantization = spqcdSize == Number_8;
            qcd.scalarExpounded = scalarExpounded;
            qcd.guardBits = sqcd >> Number_5;
            let spqcds: Spqcd[] = new Array<Spqcd>();
            while (j < length + position) {
              let spqcd: Spqcd = {};
              if (spqcdSize == Number_8) {
                spqcd.epsilon = data[j] >> Number_3;
                spqcd.mu = 0;
                j += 1;
              } else {
                spqcd.epsilon = data[j] >> Number_3;
                spqcd.mu = (data[j] & Number_0x7) << Number_8 | data[j + 1];
                j += Number_2;
              }
              spqcds.push(spqcd);
            }
            qcd.SPqcds = spqcds;
            if (context.mainHeader) {
              context.QCD = qcd;
            } else {
              context.currentTile.QCD = qcd;
              context.currentTile.QCC = new Array<QuantizationParameters>();
            }
          }
            break;
          case 0xFF5D:{
            length = this.readUint16(data, position);
            let qcc: QuantizationParameters = {};
            j = position + Number_2;
            let cqcc: number;
            if (context.SIZ.Csiz < 257) {
              cqcc = data[j];
              j += 1
            } else {
              cqcc = this.readUint16(data, j);
              j += Number_2;
            }
            let sqcd = data[j];
            j += 1;
            let spqcdSize: number = 0;
            let scalarExpounded: boolean = false;
            switch (sqcd & Number_0x1F) {
              case 0:
                spqcdSize = Number_8;
                scalarExpounded = true;
                break;
              case 1:
                spqcdSize = Number_16;
                scalarExpounded = false;
                break;
              case Number_2:
                spqcdSize = Number_16;
                scalarExpounded = true;
                break;
              default:
                error(`Invalid SQcd value ${sqcd}`);
            }
            qcc.noQuantization = spqcdSize == Number_8;
            qcc.scalarExpounded = scalarExpounded;
            qcc.guardBits = sqcd >> Number_5;
            let spqcds: Spqcd[] = new Array<Spqcd>();
            while (j < length + position) {
              let spqcd: Spqcd = {};
              if (spqcdSize == Number_8) {
                spqcd.epsilon = data[j] >> Number_3;
                spqcd.mu = 0;
                j += 1;
              } else {
                spqcd.epsilon = data[j] >> Number_3;
                spqcd.mu = ((data[j] & Number_0x7) << Number_8) | data[j + 1];
                j += Number_2;
              }
              spqcds.push(spqcd);
            }
            qcc.SPqcds = spqcds;
            if (context.mainHeader){
              context.QCC[cqcc] = qcc;
            }
            else {
              context.currentTile.QCC[cqcc] = qcc;
            }
          }
            break;
          case 0xFF52:{
            length = this.readUint16(data, position);
            let cod: CodingStyleParameters = {};
            j = position + Number_2;
            let scod:number = data[j];
            j += 1;
            cod.entropyCoderWithCustomPrecincts = (scod & 1) !== 0;
            cod.sopMarkerUsed = (scod & Number_2) !== 0;
            cod.ephMarkerUsed = (scod & Number_4) !== 0;
            cod.progressionOrder = data[j];
            j += 1
            cod.layersCount = this.readUint16(data, j);
            j += Number_2;
            cod.multipleComponentTransform = data[j];
            j += 1;
            cod.decompositionLevelsCount = data[j];
            j += 1;
            cod.xcb = (data[j] & Number_0xF) + Number_2;
            j += 1;
            cod.ycb = (data[j] & Number_0xF) + Number_2;
            j += 1;
            let blockStyle = data[j];
            j += 1;
            cod.selectiveArithmeticCodingBypass = (blockStyle & 1) !== 0;
            cod.resetContextProbabilities = (blockStyle & Number_2) !== 0;
            cod.terminationOnEachCodingPass = (blockStyle & Number_4) !== 0;
            cod.verticalyStripe = (blockStyle & Number_8) !== 0;
            cod.predictableTermination = (blockStyle & Number_16) !== 0;
            cod.segmentationSymbolUsed = (blockStyle & Number_32) !== 0;
            cod.transformation = data[j];
            j += 1;
            if (cod.entropyCoderWithCustomPrecincts) {
              let precinctsSizes: PrecinctsSize[] = new Array<PrecinctsSize>();
              while (j < length + position) {
                let precinctsSize:number = data[j];
                precinctsSizes.push({
                  PPx: precinctsSize & Number_0xF,
                  PPy: precinctsSize >> Number_4
                });
                j += 1;
              }
              cod.precinctsSizes = precinctsSizes;
            }

            if (cod.sopMarkerUsed || cod.ephMarkerUsed ||
            cod.selectiveArithmeticCodingBypass ||
            cod.resetContextProbabilities ||
            cod.terminationOnEachCodingPass ||
            cod.verticalyStripe || cod.predictableTermination ||
            cod.segmentationSymbolUsed) {
              error('Unsupported COD options: ')
            }
            if (context.mainHeader) {
              context.COD = cod;
            } else {
              context.currentTile.COD = cod;
              context.currentTile.COC = new Array<CodingStyleParameters>();
            }
          }
            break;
          case 0xFF90: {
            length = this.readUint16(data, position);
            let tile: CurrentTile = {};
            tile.index = this.readUint16(data, position + Number_2);
            tile.length = this.readUint32(data, position + Number_4);
            tile.dataEnd = tile.length! + position - Number_2;
            tile.partIndex = data[position + Number_8];
            tile.partsCount = data[position + Number_9];
            context.mainHeader = false;
            if (tile.partIndex == 0) {
              tile.COD = context.COD;
              tile.COC = context.COC as CodingStyleParameters[];
              tile.QCD = context.QCD as QuantizationParameters;
              tile.QCC = context.QCC as QuantizationParameters[];
            }
            context.currentTile = tile;
          }
            break;
          case 0xFF93: {
            let tile:CurrentTile = context.currentTile;
            if (tile.partIndex == 0) {
              this.initializeTile(context, tile.index);
              this.buildPackets(context);
            }
            length = tile.dataEnd - position;
            this.parseTilePackets(context, data, position, length);
          }
            break;
          case 0xFF64: {
            length = this.readUint16(data, position);
          }
            break;
          default:
            error(`Unknown codestream code: ${code}`);
        }
        position += length;
      }
    } catch (e) {
      warn(`JPX error: Trying to recover`);
    }
    this.tiles = this.transformComponents(context);
    this.width = context.SIZ.Xsiz - context.SIZ.XOsiz;
    this.height = context.SIZ.Ysiz - context.SIZ.YOsiz;
    this.componentsCount = context.SIZ.Csiz;
  }

  public static log2(x: number): number {
    let n:number = 1;
    let i:number = 0;
    while (x > n) {
      n <<= 1;
      i += 1;
    }
    return i;
  }

  private calculateComponentDimensions(component: Components, siz: ImageSize): void {
    let copyComponent: Components = component
    copyComponent.x0 = Math.ceil(siz.XOsiz / copyComponent.XRsiz);
    copyComponent.x1 = Math.ceil(siz.Xsiz / copyComponent.XRsiz);
    copyComponent.y0 = Math.ceil(siz.YOsiz / copyComponent.YRsiz);
    copyComponent.y1 = Math.ceil(siz.Ysiz / copyComponent.YRsiz);
    copyComponent.width = copyComponent.x1 - copyComponent.x0;
    copyComponent.height = copyComponent.y1 - copyComponent.y0;
  }

  private calculateTileGrids(context: ImageContext, components: Components[]): void {
    let siz:ImageSize = context.SIZ;
    let tiles: Tile[] = new Array<Tile>();
    let numXtiles:number = Math.ceil((siz.Xsiz - siz.XTOsiz) / siz.XTsiz);
    let numYtiles:number = Math.ceil((siz.Ysiz - siz.YTOsiz) / siz.YTsiz);

    for (let q = 0; q < numYtiles; q++) {
      for (let p = 0; p < numXtiles; p++) {
        let tile: Tile = {
          tx0: Math.max(siz.XTOsiz + p * siz.XTsiz, siz.XOsiz),
          ty0: Math.max(siz.YTOsiz + q * siz.YTsiz, siz.YOsiz),
          tx1: Math.min(siz.XTOsiz + (p + 1) * siz.XTsiz, siz.Xsiz),
          ty1: Math.min(siz.YTOsiz + (q + 1) * siz.YTsiz, siz.Ysiz),
          width: 0,
          height: 0,
          components: new Array<TileComponents>()
        };
        tile.width = tile.tx1 - tile.tx0;
        tile.height = tile.ty1 - tile.ty0;
        tiles.push(tile);
      }
    }
    context.tiles = tiles;
    const componentsCount:number = siz.Csiz;
    for (let i = 0; i < componentsCount; i++) {
      const component:Components = components[i];
      for (let j = 0; j <  tiles.length;  j++) {
        let tileComponent: TileComponents = {tcx0: 0, tcy0: 0, tcx1: 0, tcy1: 0, width: 0, height: 0}
        let tile:Tile = tiles[j];
        tileComponent.tcx0 = Math.ceil(tile.tx0 / component.XRsiz);
        tileComponent.tcy0 = Math.ceil(tile.ty0 / component.YRsiz);
        tileComponent.tcx1 = Math.ceil(tile.tx1 / component.XRsiz);
        tileComponent.tcy1 = Math.ceil(tile.ty1 / component.YRsiz);
        tileComponent.width = tileComponent.tcx1 - tileComponent.tcx0;
        tileComponent.height = tileComponent.tcy1 - tileComponent.tcy0;
        tile.components[i] = tileComponent;
      }
    }
  }

  private getBlocksDimensions(context: ImageContext, component: TileComponents, r: number): PrecinctsSize {
    let codOrCoc:CodingStyleParameters = component.codingStyleParameters!;
    let result: PrecinctsSize = {};
    if (codOrCoc.entropyCoderWithCustomPrecincts === false) {
      result.PPx = Number_15;
      result.PPy = Number_15;
    } else {
      result.PPx = (codOrCoc.precinctsSizes![r].PPx)!
      result.PPy = (codOrCoc.precinctsSizes![r].PPy)!
    }
    result.xcb = r > 0 ? Math.min(codOrCoc.xcb, result.PPx! - 1) : Math.min(codOrCoc!.xcb, result.PPx!);
    result.ycb = r > 0 ? Math.min(codOrCoc.ycb, result.PPy! - 1) : Math.min(codOrCoc!.ycb, result.PPy!);
    return result;
  }

  private buildPrecincts(context: ImageContext, resolution: Resolutions, dimensions: PrecinctsSize): void {
    let copyResolution: Resolutions = resolution;
    const precinctWidth:number = 1 << dimensions.PPx;
    const precinctHeight:number = 1 << dimensions.PPy;
    const numprecinctswide:number = copyResolution.trx1 > copyResolution.trx0 ?
      Math.ceil(copyResolution.trx1 / precinctWidth) -
      Math.floor(copyResolution.trx0 / precinctWidth) : 0;
    const numprecinctshigh:number = copyResolution.try1 > copyResolution.try0 ?
      Math.ceil(copyResolution.try1 / precinctHeight) -
      Math.floor(copyResolution.try0 / precinctHeight) : 0;
    const numprecincts:number = numprecinctswide * numprecinctshigh;
    const precinctXOffset:number = Math.floor(copyResolution.trx0 / precinctWidth) *
      precinctWidth;
    const precinctYOffset:number = Math.floor(copyResolution.try0 / precinctHeight) *
      precinctHeight;
    copyResolution.precinctParameters = {
      precinctXOffset: precinctXOffset,
      precinctYOffset: precinctYOffset,
      precinctWidth: precinctWidth,
      precinctHeight: precinctHeight,
      numprecinctswide: numprecinctswide,
      numprecinctshigh: numprecinctshigh,
      numprecincts: numprecincts
    };
  }

  private buildCodeblocks(context: ImageContext, subband: Subband, dimensions: PrecinctsSize): void {
    let copySubband:Subband = subband
    const xcb_: number = dimensions.xcb;
    const ycb_: number = dimensions.ycb;
    const codeblockWidth: number = 1 << xcb_;
    const codeblockHeight: number = 1 << ycb_;
    const cbx0: number = Math.floor(subband.tbx0 / codeblockWidth);
    const cby0: number = Math.floor(subband.tby0 / codeblockHeight);
    const cbx1: number = Math.ceil(subband.tbx1 / codeblockWidth);
    const cby1: number = Math.ceil(subband.tby1 / codeblockHeight);
    const precinctParameters: PrecinctParameters = subband.resolution.precinctParameters;
    let codeblocks: Codeblock[] = new Array<Codeblock>();
    let precincts: Precinct[] = new Array<Precinct>();

    for (let j = cby0; j < cby1; j++) {
      for (let i = cbx0; i < cbx1; i++) {
        let codeblock: Codeblock = {
          cbx: i,
          cby: j,
          tbx0: codeblockWidth * i,
          tby0: codeblockHeight * j,
          tbx1: codeblockWidth * (i + 1),
          tby1: codeblockHeight * (j + 1)
        };

        const pi: number = Math.floor((
          codeblock.tbx0 -
          precinctParameters.precinctXOffset) / precinctParameters.precinctWidth);
        const pj: number = Math.floor((
          codeblock.tby0 -
          precinctParameters.precinctYOffset) / precinctParameters.precinctHeight);
        const precinctNumber: number = pj + pi * precinctParameters.numprecinctswide;
        codeblock.tbx0 = Math.max(subband.tbx0, codeblock.tbx0);
        codeblock.tby0 = Math.max(subband.tby0, codeblock.tby0);
        codeblock.tbx1 = Math.min(subband.tbx1, codeblock.tbx1);
        codeblock.tby1 = Math.min(subband.tby1, codeblock.tby1);
        codeblock.precinctNumber = precinctNumber;
        codeblock.subbandType = subband.type;
        let coefficientsLength: number = (codeblock.tbx1 - codeblock.tbx0) * (codeblock.tby1 - codeblock.tby0);
        codeblock.Lblock = Number_3;
        codeblocks.push(codeblock);

        let precinct: Precinct;

        if (precinctNumber in precincts) {
          precinct = precincts[precinctNumber];
          precinct.cbxMin = Math.min(precinct.cbxMin, i);
          precinct.cbyMin = Math.min(precinct.cbyMin, j);
          precinct.cbxMax = Math.max(precinct.cbxMax, i);
          precinct.cbyMax = Math.max(precinct.cbyMax, j);
        } else {
          precinct = { cbxMin: i, cbyMin: j, cbxMax: i, cbyMax: j };
          precincts[precinctNumber] = precinct;
        }
        codeblock.precinct = precinct;
      }
    }
    copySubband.codeblockParameters = {
      codeblockWidth: xcb_,
      codeblockHeight: ycb_,
      numcodeblockwide: cbx1 - cbx0 + 1,
      numcodeblockhigh: cby1 - cby1 + 1
    };

    copySubband.codeblocks = codeblocks;
    for (let i = 0; i < codeblocks.length;i++) {
      const codeblock: Codeblock = codeblocks[i];
      const precinctNumber: number = codeblock.precinctNumber;
    }
    copySubband.precincts = precincts;
  }

  private createPacket(resolution: Resolutions, precinctNumber: number, layerNumber: number): PacketsIterator {
    let precinctCodeblocks: Codeblock[] = new Array<Codeblock>();
    let subbands: Subband[] = resolution.subbands;
    for (let i = 0; i < subbands.length; i++) {
      const subband: Subband = subbands[i];
      const codeblocks: Codeblock[] = subband.codeblocks;
      for (let j = 0; j < codeblocks.length; j++) {
        const codeblock: Codeblock = codeblocks[j];
        if (codeblock.precinctNumber !== precinctNumber) {
          continue;
        }
        precinctCodeblocks.push(codeblock);
      }
    }
    return new PacketsIterator(layerNumber,precinctCodeblocks);
  }

  private LayerResolutionComponentPositionIterator(context: ImageContext): PacketsIterator {
    const siz:ImageSize = context.SIZ;
    const tileIndex: number = context.currentTile.index;
    const tile: Tile = context.tiles[tileIndex];
    const layersCount: number = tile.codingStyleDefaultParameters.layersCount;
    const componentsCount: number = siz.Csiz;
    let maxDecompositionLevelsCount: number = 0;
    for (let q = 0; q < componentsCount; q++) {
      maxDecompositionLevelsCount = Math.max(
        maxDecompositionLevelsCount,
        tile.components[q].codingStyleParameters.decompositionLevelsCount);
    }

    let l: number = 0;
    let r: number = 0;
    let i: number = 0;
    let k: number = 0;
    while (l < layersCount) {
      while (r <= maxDecompositionLevelsCount) {
        while (i < componentsCount) {
          const component: TileComponents = tile.components![i]
          if (r > (component.codingStyleParameters?.decompositionLevelsCount!)!) {
            continue
          }

          const resolution: Resolutions = component.resolutions![r]
          const numprecincts: number = resolution.precinctParameters?.numprecincts
          while (k < numprecincts!) {
            const packet:PacketsIterator = this.createPacket(resolution, k, l)
            k += 1
            return packet
          }
          k = 0
          i += 1
        }
        i = 0
        r += 1
      }
      r = 0
      l += 1
    }
    throw new Error('Out of packets');
  }

  private ResolutionLayerComponentPositionIterator(context: ImageContext):PacketsIterator {
    const siz: ImageSize = context.SIZ;
    const tileIndex: number = context.currentTile.index;
    const tile: Tile = context.tiles[tileIndex];
    const layersCount: number = tile.codingStyleDefaultParameters.layersCount;
    const componentsCount: number = siz.Csiz;
    let maxDecompositionLevelsCount: number = 0;
    for (let q:number = 0; q < componentsCount; q++) {
      maxDecompositionLevelsCount = Math.max(
        maxDecompositionLevelsCount,
        tile.components[q].codingStyleParameters.decompositionLevelsCount);
    }
    let l: number = 0;
    let r: number = 0;
    let i: number = 0;
    let k: number = 0;
    while (r <= maxDecompositionLevelsCount) {
      while (l < layersCount) {
        while (i < componentsCount) {
          const component:TileComponents = tile.components![i]
          if (r > (component.codingStyleParameters?.decompositionLevelsCount)!) {
            continue
          }
          const resolution:Resolutions = component.resolutions![r]
          const numprecincts:number = resolution.precinctParameters?.numprecincts

          while (k < numprecincts!) {
            const packet:PacketsIterator = this.createPacket(resolution, k, l)
            k += 1
            return packet
          }
          k = 0
          i += 1
        }
        i = 0
        l += 1
      }
      l = 0
      r += 1
    }
    throw new Error('Out of packets');
  }

  private buildPackets(context: ImageContext) {
    let siz:ImageSize = context.SIZ;
    let tileIndex:number = context.currentTile.index;
    let tile:Tile = context.tiles[tileIndex];
    let componentsCount:number = siz.Csiz;

    for (let c = 0; c < componentsCount; c++) {
      let component: TileComponents = tile.components[c];
      let decompositionLevelsCount: number = component.codingStyleParameters.decompositionLevelsCount;

      let resolutions: Resolutions[] = new Array<Resolutions>();
      let subbands: Subband[] = new Array<Subband>();
      for (let r = 0; r <= decompositionLevelsCount; r++) {
        const blocksDimensions:PrecinctsSize = this.getBlocksDimensions(context, component, r);
        let resolution: Resolutions = {};
        const scale = 1 << (decompositionLevelsCount - r);
        resolution.trx0 = Math.ceil(component.tcx0 / scale);
        resolution.try0 = Math.ceil(component.tcy0 / scale);
        resolution.trx1 = Math.ceil(component.tcx1 / scale);
        resolution.try1 = Math.ceil(component.tcy1 / scale);
        this.buildPrecincts(context, resolution, blocksDimensions);
        resolutions.push(resolution);

        let subband: Subband;
        if (r === 0) {
          subband = {};
          subband.type = 'LL';
          subband.tbx0 = Math.ceil(component.tcx0 / scale);
          subband.tby0 = Math.ceil(component.tcy0 / scale);
          subband.tbx1 = Math.ceil(component.tcx1 / scale);
          subband.tby1 = Math.ceil(component.tcy1 / scale);
          subband.resolution = resolution;
          this.buildCodeblocks(context, subband, blocksDimensions);
          subbands.push(subband);
          resolution.subbands = [subband];
        } else {
          let bscale = 1 << (decompositionLevelsCount - r + 1);
          let resolutionSubbands: Subband[] = new Array<Subband>();
          subband = {};
          subband.type = 'HL';
          subband.tbx0 = Math.ceil(component.tcx0 / bscale - 0.5);
          subband.tby0 = Math.ceil(component.tcy0 / bscale);
          subband.tbx1 = Math.ceil(component.tcx1 / bscale - 0.5);
          subband.tby1 = Math.ceil(component.tcy1 / bscale);
          subband.resolution = resolution;
          this.buildCodeblocks(context, subband, blocksDimensions);
          subbands.push(subband);
          resolutionSubbands.push(subband);

          subband = {};
          subband.type = 'LH';
          subband.tbx0 = Math.ceil(component.tcx0 / bscale);
          subband.tby0 = Math.ceil(component.tcy0 / bscale - 0.5);
          subband.tbx1 = Math.ceil(component.tcx1 / bscale);
          subband.tby1 = Math.ceil(component.tcy1 / bscale - 0.5);
          subband.resolution = resolution;
          this.buildCodeblocks(context, subband, blocksDimensions);
          subbands.push(subband);
          resolutionSubbands.push(subband);

          subband = {};
          subband.type = 'HH';
          subband.tbx0 = Math.ceil(component.tcx0 / bscale - 0.5);
          subband.tby0 = Math.ceil(component.tcy0 / bscale - 0.5);
          subband.tbx1 = Math.ceil(component.tcx1 / bscale - 0.5);
          subband.tby1 = Math.ceil(component.tcy1 / bscale - 0.5);
          subband.resolution = resolution;
          this.buildCodeblocks(context, subband, blocksDimensions);
          subbands.push(subband);
          resolutionSubbands.push(subband);

          resolution.subbands = resolutionSubbands;
        }
      }

      component.resolutions = resolutions;
      component.subbands = subbands;
    }
    const progressionOrder: number = tile.codingStyleDefaultParameters.progressionOrder;
    switch (progressionOrder) {
      case 0:
        tile.packetsIterator =  this.LayerResolutionComponentPositionIterator(context);
        break;
      case 1:
        tile.packetsIterator =  this.ResolutionLayerComponentPositionIterator(context);
        break;
      default:
        error(`Unsupported progression order ${progressionOrder}`);
    }
  }

  private parseTilePackets(context: ImageContext, data: Uint8Array, offset: number, dataLength: number): number {
    let position: number = 0;
    let buffer: number;
    let bufferSize: number = 0;
    let skipNextBit: boolean = false;
    let readBits = (count: number):number => {
      while (bufferSize < count) {
        let b = data[offset + position];
        position += 1;

        if (skipNextBit) {
          buffer = (buffer << Number_7) | b;
          bufferSize += Number_7;
          skipNextBit = false;
        } else {
          buffer = (buffer << Number_8) | b;
          bufferSize += Number_8;
        }

        if (b == Number_0xFF) {
          skipNextBit = true;
        }
      }
      bufferSize -= count;
      return (buffer >>> bufferSize) & ((1 << count) - 1);
    }
    let alignToByte = (): void => {
      bufferSize = 0;
      if (skipNextBit) {
        position += 1;
        skipNextBit = false;
      }
    }
    let readCodingpasses = ():number => {
      let value: number = readBits(1);
      if (value == 0)
        return 1;
      value = (value << 1) | readBits(1);
      if (value == Number_0x02)
        return Number_2;
      value = (value << Number_2) | readBits(Number_2);
      if (value <= Number_0x0E)
        return (value & Number_0x03) + Number_3;
      value = (value << Number_5) | readBits(Number_5);
      if (value <= Number_0x1FE)
        return (value & Number_0x1F) + Number_6;
      value = (value << Number_7) | readBits(Number_7);
      return (value & Number_0x7F) + Number_37;
    }
    const tileIndex: number = context.currentTile.index;
    const tile: Tile = context.tiles[tileIndex];
    const packetsIterator: PacketsIterator = tile.packetsIterator;
    while (position < dataLength) {
      const packet: PacketsIterator = packetsIterator.nextPacket();
      if (readBits(1) === 0) {
        alignToByte();
        continue;
      }
      const layerNumber: number = packet.layerNumber;
      let queue: PacketItem[] = new Array<PacketItem>();
      for (let i: number = 0; i < packet.codeblocks.length; i++) {
        let codeblock: Codeblock = packet.codeblocks[i];
        const precinct: Precinct = codeblock.precinct;
        const codeblockColumn: number = codeblock.cbx - precinct.cbxMin;
        const codeblockRow: number = codeblock.cby - precinct.cbyMin;
        let codeblockIncluded: boolean = false;
        let firstTimeInclusion: boolean = false;

        if (codeblock.included !== null) {
          codeblockIncluded = readBits(1) != 0
        } else {
          let precinct: Precinct = codeblock.precinct;
          let inclusionTree: InclusionTree;
          let zeroBitPlanesTree: TagTree;
          if (precinct.inclusionTree !== null && precinct.inclusionTree !== undefined){
            inclusionTree = precinct.inclusionTree;
          } else {
            const width: number = precinct.cbxMax - precinct.cbxMin + 1;
            const height: number = precinct.cbyMax - precinct.cbyMin + 1;
            inclusionTree = new InclusionTree(width, height, layerNumber);
            zeroBitPlanesTree = new TagTree(width, height);
            precinct.inclusionTree = inclusionTree;
            precinct.zeroBitPlanesTree = zeroBitPlanesTree;
          }
          if (inclusionTree.reset(codeblockColumn, codeblockRow, layerNumber)) {
            while (true) {
              if (readBits(1) !== 0) {
                let valueReady:boolean = !inclusionTree.nextLevel();
                if (valueReady) {
                  codeblock.included = true;
                  firstTimeInclusion = true;
                  codeblockIncluded = firstTimeInclusion;
                  break;
                }
              } else {
                inclusionTree.incrementValue(layerNumber);
                break;
              }
            }
          }
        }

        if (!codeblockIncluded) {
          continue;
        }
        if (firstTimeInclusion) {
          const zeroBitPlanesTree:TagTree = precinct.zeroBitPlanesTree;
          zeroBitPlanesTree.reset(codeblockColumn, codeblockRow);
          while (true) {
            if (readBits(1)!==0) {
              const valueReady:boolean = !zeroBitPlanesTree.nextLevel();
              if (valueReady) {
                break;
	      }
            } else {
              zeroBitPlanesTree.incrementValue();
	    }
          }
          codeblock.zeroBitPlanes = zeroBitPlanesTree.value;
        }
        const codingpasses: number = readCodingpasses();
        while (readBits(1) !== 0) {
          codeblock.Lblock += 1;
        }
        const codingpassesLog2: number = JpxImage.log2(codingpasses);
        const bits:number = ((codingpasses < (1 << codingpassesLog2)) ?
          codingpassesLog2 - 1 : codingpassesLog2) + codeblock.Lblock;
        let codedDataLength: number = readBits(bits);
        const item: PacketItem = {
          codeblock: codeblock,
          codingpasses: codingpasses,
          dataLength: codedDataLength
        };
        queue.push(item);
      }
      alignToByte();
      while (queue.length > 0) {
        const packetItem:PacketItem = queue.shift();
        const codeblock: Codeblock = packetItem.codeblock;
        if (codeblock.data === null) {
          codeblock.data = new Array<CodeblockData>();
        }
        const blockData: CodeblockData = {
          data: data,
          start: offset + position,
          end: offset + position + packetItem.dataLength,
          codingpasses: packetItem.codingpasses
        };
        codeblock.data.push(blockData);
        position += packetItem.dataLength;
      }
    }
    return position
  }


  private copyCoefficients(coefficients:Uint32Array, x0:number, y0:number, width:number, height:number, delta:number, mb:number, codeblocks: Codeblock[], transformation:number) {
    let copyCoefficients:Uint32Array = coefficients;
    const r: number = 0.5;
    for (let i = 0; i < codeblocks.length; ++i) {
      const codeblock: Codeblock = codeblocks[i];
      const blockWidth: number = codeblock.tbx1 - codeblock.tbx0;
      const blockHeight: number = codeblock.tby1 - codeblock.tby0;
      if (blockWidth == 0 || blockHeight == 0) {
        continue;
      }
      if (codeblock.data === null) {
        continue;
      }
      let bitModel: BitModel = new BitModel(blockWidth, blockHeight, codeblock.subbandType, codeblock.zeroBitPlanes);

      let currentCodingpassType: number = Number_2;
      let data: CodeblockData[] = codeblock.data;
      let totalLength: number = 0;
      let codingpasses: number = 0;
      for (let q = 0; q < data.length; q++) {
        const dataItem: CodeblockData = data[q];
        totalLength += dataItem.end - dataItem.start;
        codingpasses += dataItem.codingpasses;
      }
      let encodedData: Uint8Array = new Uint8Array(totalLength);
      let k: number = 0;
      for (let q = 0; q < data.length; q++) {
        const dataItem: CodeblockData = data[q];
        const chunk:Uint8Array = dataItem.data.subarray(dataItem.start, dataItem.end);
        encodedData.set(chunk, k);
        k += chunk.length;
      }
      let decoder: ArithmeticDecoder = new ArithmeticDecoder(encodedData, 0, totalLength);
      bitModel.setDecoder(decoder);

      for (let q = 0; q < codingpasses; q++) {
        switch (currentCodingpassType) {
          case 0:
            bitModel.runSignificancePropogationPass();
            break;
          case 1:
            bitModel.runMagnitudeRefinementPass();
            break;
          case Number_2:
            bitModel.runCleanupPass();
            break;
          default:
            break;
        }
        currentCodingpassType = (currentCodingpassType + 1) % Number_3;
      }

      let offset:number = (codeblock.tbx0 - x0) + (codeblock.tby0 - y0) * width;
      let position:number = 0;
      for (let j = 0; j < blockHeight; j++) {
        for (let k = 0; k < blockWidth; k++) {
          let n = (bitModel.coefficentsSign[position] !== 0 ? -1 : 1) * bitModel.coefficentsMagnitude[position];
          const nb: number = bitModel.bitsDecoded[position];
          let correction:number;
          if (transformation == 0 || mb > nb) {
            n += n < 0 ? n - r : n > 0 ? n + r : 0;
            correction =  Math.pow(Number_2, mb - nb);
          } else {
            correction = 1;
          }
          copyCoefficients[offset] = n * correction * delta;
          offset += 1;
          position += 1;
        }
        offset += width - blockWidth;
      }
    }
  }

  private transformTile(context: ImageContext, tile: Tile, c): TransformResult {
    const component: TileComponents = tile.components[c];
    const codingStyleParameters: CodingStyleParameters = component.codingStyleParameters;
    const quantizationParameters: QuantizationParameters = component.quantizationParameters;
    const decompositionLevelsCount: number = codingStyleParameters.decompositionLevelsCount;
    const spqcds: Spqcd[] = quantizationParameters.SPqcds;
    const scalarExpounded: boolean = quantizationParameters.scalarExpounded;
    const guardBits: number = quantizationParameters.guardBits;
    const transformation: number = codingStyleParameters.transformation;
    const precision: number = context.components[c].precision;
    let subbandCoefficients: TransformResult[] = new Array<TransformResult>();
    let b: number = 0;
    for (let i = 0; i <= decompositionLevelsCount; i++) {
      let resolution: Resolutions = component.resolutions[i];
      for (let j = 0; j < resolution.subbands.length; j++) {
        let mu: number;
        let epsilon: number;
        if (!scalarExpounded) {
          mu = spqcds[0].mu;
          epsilon = spqcds[0].epsilon + (i > 0 ? 1 - i : 0);
        } else {
          mu = spqcds[b].mu;
          epsilon = spqcds[b].epsilon;
        }
        const subband: Subband = resolution.subbands[j];
        const width: number = subband.tbx1 - subband.tbx0;
        const height: number = subband.tby1 - subband.tby0;
        const gainLog2: number = this.SubbandsGainLog2[subband.type];
        const delta: number = Math.pow(Number_2, (precision + gainLog2) - epsilon) *
          (1 + mu / 2048);
        let mb = guardBits + epsilon - 1;
        let coefficients:Uint32Array = new Uint32Array(width * height);
        this.copyCoefficients(coefficients, subband.tbx0, subband.tby0,width, height, delta, mb, subband.codeblocks, transformation);
        let transModel: TransformResult = { width: width, height: height, items: Uint8Array.from(coefficients)};
        subbandCoefficients.push(transModel);
        b += 1;
      }
    }
    const transform: Transform = transformation == 0 ? new IrreversibleTransform() : new ReversibleTransform();
    const result: TransformResult = transform.calculate(subbandCoefficients, component.tcx0, component.tcy0);
    const transResult: TransformResult = {
      left: component.tcx0,
      top: component.tcy0,
      width: result.width,
      height: result.height,
      items: result.items
    };
    return transResult;
  }

  private transformComponents(context: ImageContext) {
    const siz: ImageSize = context.SIZ;
    const components: Components[] = context.components;
    const componentsCount: number = siz.Csiz;
    let resultImages: TransformResult[][] = new Array<TransformResult[]>();

    for (let i = 0; i < context.tiles.length; i++) {
      const tile:Tile = context.tiles[i];
      let result: TransformResult[] = new Array<TransformResult>();
      for (let c = 0; c < componentsCount; c++) {
        const image:TransformResult = this.transformTile(context, tile, c);
        result.push(image);
      }
      if (tile.codingStyleDefaultParameters.multipleComponentTransform > 0) {
        let y0items: Uint8Array = result[0].items;
        let y1items: Uint8Array = result[1].items;
        let y2items: Uint8Array = result[Number_2].items;

        for (let j: number = 0; j < y0items.length; j++) {
          const y0: number = y0items[j];
          const y1: number = y1items[j];
          const y2: number = y2items[j];
          const i1: number = y0 - ((y2 + y1) >> Number_2);
          y1items[j] = i1;
          y0items[j] = y2 + i1;
          y2items[j] = y1 + i1;
        }
      }

      for (let c = 0; c < componentsCount; c++) {
        const component: Components = components[c];
        if (component.isSigned) {
          continue;
        }
        const offset = 1 << (component.precision - 1);
        const tileImage: TransformResult = result[c];
        let items: Uint8Array = tileImage.items;
        for (let j: number = 0; j < items.length; j++){
          items[j] += offset;
        }
      }

      for (let c = 0; c < componentsCount; c++) {
        const component: Components = components[c];
        const offset: number = component.isSigned ? Number_128 : 0;
        const shift: number = component.precision - Number_8;
        const tileImage: TransformResult = result[c];
        const items: Uint8Array = tileImage.items;
        let data: Uint8Array = new Uint8Array(items.length);
        for (let j: number = 0; j < items.length; j++) {
          const value: number = (items[j] >> shift) + offset;
          data[j] = value < 0 ? 0 : value > Number_255 ? Number_255 : value;
        }
        result[c].items = data;
      }
      resultImages.push(result);
    }
    return resultImages;
  }

  private initializeTile(context: ImageContext, tileIndex: number) {
    const siz: ImageSize = context.SIZ;
    const componentsCount: number = siz.Csiz;
    let tile: Tile = context.tiles[tileIndex];
    for (let c = 0; c < componentsCount; c++) {
      const component: TileComponents = tile.components[c];
      const qcdOrQcc: QuantizationParameters = context.currentTile.QCC[c] ?? context.currentTile.QCD;
      component.quantizationParameters = qcdOrQcc;
      let codOrCoc: CodingStyleParameters = context.currentTile.COC[c] ?? context.currentTile.COD;
      component.codingStyleParameters = codOrCoc;
    }

    tile.codingStyleDefaultParameters = context.currentTile.COD;
  }
}

interface TagTreeLevel {
  width: number;
  height: number;
  items: number[];
  index?: number;}

class TagTree {
  private levels: TagTreeLevel[];
  private currentLevel: number = 0;
  value?: number;

  constructor(width: number, height: number) {
    let copyWidth:number = width;
    let copyHeight:number = height;
    const levelsLength: number = JpxImage.log2(Math.max(copyWidth, copyHeight)) + 1;
    this.levels = new Array<TagTreeLevel>();
    for (let i = 0; i < levelsLength; i++) {
      const level: TagTreeLevel = { width:copyWidth, height:copyHeight, items: new Array<number>() };
      this.levels.push(level);
      copyWidth = Math.ceil(copyWidth / Number_2);
      copyHeight = Math.ceil(copyHeight / Number_2);
    }
  }

  reset(i: number, j: number): void {
    let i1:number = i;
    let j1:number = j;
    let currentLevel:number = 0;
    let value:number = 0;
    while (currentLevel < this.levels.length) {
      let level:TagTreeLevel = this.levels[currentLevel];
      const index:number = i1 + j1 * level.width;
      if (index < level.items.length) {
        value = level.items[index];
        break;
      }
      level.index = index;
      i1 >>= 1;
      j1 >>= 1;
      currentLevel += 1;
    }
    currentLevel -= 1;
    let level:TagTreeLevel = this.levels[currentLevel];
    level.items[level.index!] = value;
    this.currentLevel = currentLevel;
    this.value = null;
  }

  incrementValue(): void {
    let level:TagTreeLevel = this.levels[this.currentLevel];
    level.items[level.index!] += 1;
  }

  nextLevel(): boolean {
    let currentLevel:number = this.currentLevel;
    const level:TagTreeLevel = this.levels[currentLevel];
    let value:number = level.items[level.index!];
    currentLevel -= 1;
    if (currentLevel < 0) {
      this.value = value;
      return false;
    }
    this.currentLevel = currentLevel;
    let nextLevel:TagTreeLevel = this.levels[currentLevel];
    nextLevel.items[level.index!] = value;
    return true;
  }
}

interface InclusionTreeLevel {
  width: number;
  height: number;
  items: Uint8Array;
  index?: number;
}

class InclusionTree {
  private levels: InclusionTreeLevel[];
  private currentLevel: number = 0;

  constructor(width: number, height: number, defaultValue: number) {
    let copyWidth: number = width;
    let copyHeight: number = height;
    const levelsLength:number = Math.log2(Math.max(copyWidth, copyHeight)) + 1;
    this.levels = new Array<InclusionTreeLevel>();
    for (let i = 0; i < levelsLength; i++) {
      let items = new Uint8Array(width * height);
      items.fill(defaultValue);
      for (let j =0; j < items.length; j++) {
        items[j] = defaultValue
        const level: InclusionTreeLevel = { width:copyWidth, height:copyHeight, items:items };
        this.levels.push(level);
        copyWidth = Math.ceil(copyWidth / Number_2);
        copyHeight = Math.ceil(copyHeight / Number_2);
      }
    }
  }

  reset(i: number, j: number, stopValue: number): boolean {
    let currentLevel:number = 0;
    let il: number = i;
    let jl: number = j;
    while (currentLevel < this.levels.length) {
      let level:InclusionTreeLevel = this.levels[currentLevel];
      const index:number = il + jl* level.width;
      level.index = index;
      const value:number = level.items[index];
      if (value === Number_0xFF) {
        break;
      }
      if (value > stopValue) {
        this.currentLevel = currentLevel;
        this.propagateValues();
        return false;
      }
      il >>= 1;
      jl >>= 1;
      currentLevel += 1;
    }
    this.currentLevel = currentLevel - 1;
    return true;
  }

  incrementValue(stopValue: number): void {
    let level:InclusionTreeLevel = this.levels[this.currentLevel];
    level.items[level.index] = stopValue + 1;
    this.propagateValues();
  }

  propagateValues(): void {
    let levelIndex:number = this.currentLevel;
    const level:InclusionTreeLevel = this.levels[levelIndex]
    let currentValue:number = level.items[level.index]
    while (levelIndex >= 0) {
      let level: InclusionTreeLevel = this.levels[levelIndex];
      level.items[level.index!] = currentValue;
      levelIndex -= 1;
    }
  }

  nextLevel(): boolean {
    let currentLevel:number = this.currentLevel;
    let level:InclusionTreeLevel = this.levels[currentLevel];
    const value:number = level.items[level.index!];
    level.items[level.index!] = Number_0xFF;
    currentLevel -= 1;
    if (currentLevel < 0) {
      return false;
    }
    this.currentLevel = currentLevel;
    let nextLevel:InclusionTreeLevel = this.levels[currentLevel];
    nextLevel.items[level.index!] = value;
    return true;
  }
}

class QeTableClass {
  qe: number
  nmps: number
  nlps: number
  switchFlag: number
}
class ArithmeticDecoder {
  private data: Uint8Array;
  private bp: number;
  private dataEnd: number;
  private chigh: number;
  private clow: number;
  private ct: number;
  private a: number;
  private qeTable: QeTableClass[] = [
    { qe: 0x5601, nmps: 1, nlps: 1, switchFlag: 1 }, { qe: 0x3401, nmps: 2, nlps: 6, switchFlag: 0 }, { qe: 0x1801, nmps: 3, nlps: 9, switchFlag: 0 }, { qe: 0x0AC1, nmps: 4, nlps: 12, switchFlag: 0 }, { qe: 0x0521, nmps: 5, nlps: 29, switchFlag: 0 }, { qe: 0x0221, nmps: 38, nlps: 33, switchFlag: 0 }, { qe: 0x5601, nmps: 7, nlps: 6, switchFlag: 1 }, { qe: 0x5401, nmps: 8, nlps: 14, switchFlag: 0 }, { qe: 0x4801, nmps: 9, nlps: 14, switchFlag: 0 }, { qe: 0x3801, nmps: 10, nlps: 14, switchFlag: 0 }, { qe: 0x3001, nmps: 11, nlps: 17, switchFlag: 0 }, { qe: 0x2401, nmps: 12, nlps: 18, switchFlag: 0 }, { qe: 0x1C01, nmps: 13, nlps: 20, switchFlag: 0 }, { qe: 0x1601, nmps: 29, nlps: 21, switchFlag: 0 }, { qe: 0x5601, nmps: 15, nlps: 14, switchFlag: 1 }, { qe: 0x5401, nmps: 16, nlps: 14, switchFlag: 0 }, { qe: 0x5101, nmps: 17, nlps: 15, switchFlag: 0 }, { qe: 0x4801, nmps: 18, nlps: 16, switchFlag: 0 }, { qe: 0x3801, nmps: 19, nlps: 17, switchFlag: 0 }, { qe: 0x3401, nmps: 20, nlps: 18, switchFlag: 0 }, { qe: 0x3001, nmps: 21, nlps: 19, switchFlag: 0 }, { qe: 0x2801, nmps: 22, nlps: 19, switchFlag: 0 }, { qe: 0x2401, nmps: 23, nlps: 20, switchFlag: 0 }, { qe: 0x2201, nmps: 24, nlps: 21, switchFlag: 0 }, { qe: 0x1C01, nmps: 25, nlps: 22, switchFlag: 0 }, { qe: 0x1801, nmps: 26, nlps: 23, switchFlag: 0 }, { qe: 0x1601, nmps: 27, nlps: 24, switchFlag: 0 }, { qe: 0x1401, nmps: 28, nlps: 25, switchFlag: 0 }, { qe: 0x1201, nmps: 29, nlps: 26, switchFlag: 0 }, { qe: 0x1101, nmps: 30, nlps: 27, switchFlag: 0 }, { qe: 0x0AC1, nmps: 31, nlps: 28, switchFlag: 0 }, { qe: 0x09C1, nmps: 32, nlps: 29, switchFlag: 0 }, { qe: 0x08A1, nmps: 33, nlps: 30, switchFlag: 0 }, { qe: 0x0521, nmps: 34, nlps: 31, switchFlag: 0 }, { qe: 0x0441, nmps: 35, nlps: 32, switchFlag: 0 }, { qe: 0x02A1, nmps: 36, nlps: 33, switchFlag: 0 }, { qe: 0x0221, nmps: 37, nlps: 34, switchFlag: 0 }, { qe: 0x0141, nmps: 38, nlps: 35, switchFlag: 0 }, { qe: 0x0111, nmps: 39, nlps: 36, switchFlag: 0 }, { qe: 0x0085, nmps: 40, nlps: 37, switchFlag: 0 }, { qe: 0x0049, nmps: 41, nlps: 38, switchFlag: 0 }, { qe: 0x0025, nmps: 42, nlps: 39, switchFlag: 0 }, { qe: 0x0015, nmps: 43, nlps: 40, switchFlag: 0 }, { qe: 0x0009, nmps: 44, nlps: 41, switchFlag: 0 }, { qe: 0x0005, nmps: 45, nlps: 42, switchFlag: 0 }, { qe: 0x0001, nmps: 45, nlps: 43, switchFlag: 0 }, { qe: 0x5601, nmps: 46, nlps: 46, switchFlag: 0 }
  ];

  constructor(data: Uint8Array, start: number, end: number) {
    this.data = data;
    this.bp = start;
    this.dataEnd = end;
    this.chigh = data[start];
    this.clow = 0;

    this.chigh = ((this.chigh << Number_7) & Number_0xFFFF) | ((this.clow >> Number_9) & Number_0x7F);
    this.clow = (this.clow << Number_7) & Number_0xFFFF;
    this.ct -= Number_7;
    this.a = Number_0x8000;
    this.byteIn();
  }

  private byteIn(): void {
    let data:Uint8Array = this.data;
    let bp:number = this.bp;
    if (data[bp] === Number_0xFF) {
      let b1:number = data[bp + 1];
      if (b1 > Number_0x8F) {
        this.clow += Number_0xFF00;
        this.ct = Number_8;
      } else {
        bp += 1;
        this.clow += (data[bp] << Number_9);
        this.ct = Number_7;
        this.bp = bp;
      }
    } else {
      bp += 1;
      this.clow += bp < this.dataEnd ? (data[bp] << Number_8) : Number_0xFF00;
      this.ct = Number_8;
      this.bp = bp;
    }
    if (this.clow > Number_0xFFFF) {
      this.chigh += (this.clow >> Number_16);
      this.clow &= Number_0xFFFF;
    }
  }

  readBit(cx: [index: number, mps: number]): number {
    const qeIcx: number = this.qeTable[cx[0]].qe;
    this.a -= qeIcx;

    if (this.chigh < qeIcx) {
      const d:number = this.exchangeLps(cx);
      this.renormD();
      return d;
    } else {
      this.chigh -= qeIcx;
      if ((this.a & Number_0x8000) == 0) {
        const d:number = this.exchangeMps(cx);
        this.renormD();
        return d;
      } else {
        return cx[1];
      }
    }
  }

  private renormD(): void {
    do {
      if (this.ct == 0){
        this.byteIn();
      }
      this.a <<= 1;
      this.chigh = ((this.chigh << 1) & Number_0xFFFF) | ((this.clow >> Number_15) & 1);
      this.clow = (this.clow << 1) & Number_0xFFFF;
      this.ct -= 1;
    } while ((this.a & Number_0x8000) == 0);
  }

  private exchangeMps(cx: [index: number, mps: number]): number {
    let copyCx:[index: number, mps: number] = cx
    let d: number;
    const qeTableIcx: QeTableClass = this.qeTable[cx[0]];
    if (this.a < qeTableIcx.qe) {
      d = 1 - copyCx[1];
      if (qeTableIcx.switchFlag == 1) {
        copyCx[1] = 1 - copyCx[1];
      }
      copyCx[0] = qeTableIcx.nlps;
    } else {
      d = copyCx[1];
      copyCx[0] = qeTableIcx.nmps;
    }
    return d;
  }

  private exchangeLps(cx: [index: number, mps: number]): number {
    let copyCx:[index: number, mps: number] = cx
    let d: number;
    const qeTableIcx: QeTableClass = this.qeTable[copyCx[0]];
    if (this.a < qeTableIcx.qe) {
      this.a = qeTableIcx.qe;
      d = copyCx[1];
      copyCx[0] = qeTableIcx.nmps;
    } else {
      this.a = qeTableIcx.qe;
      d = 1 - copyCx[1];
      if (qeTableIcx.switchFlag == 1) {
        copyCx[1] = 1 - copyCx[1];
      }
      copyCx[0] = qeTableIcx.nlps;
    }
    return d;
  }
}

class IrreversibleTransform extends Transform {
  constructor() {
    super();
  }

  filter(y: number[], offset: number, length: number, i0: number, x: number[]) {
    let copyX: number[] = x;
    const i0_:number = Math.floor(i0 / Number_2);
    const i1_:number = Math.floor((i0 + length) / Number_2);
    const offset_:number = offset - (i0 % 1);

    const alpha:number = -1.586134342059924;
    const beta:number = -0.052980118572961;
    const gamma:number = 0.882911075530934;
    const delta:number = 0.443506852043971;
    const K:number = 1.230174104914001;
    const K_:number = 1 / K;


    let j: number = offset_ - Number_2;
    let n: number = i0_ - 1;
    let nn: number = i1_ + Number_2;
    while (n < nn) {
      copyX[j] = K * y[j];
      n += 1;
      j += Number_2;
    }

    j = offset_ - Number_3;
    n = i0_ - Number_2;
    nn = i1_ + Number_2;
    while (n < nn) {
      copyX[j] = K_ * y[j];
      n += 1;
      j += Number_2;
    }

    j = offset_ - Number_2;
    n = i0_ - 1;
    nn = i1_ + Number_2;
    while (n < nn) {
      copyX[j] -= delta * (copyX[j - 1] + copyX[j + 1]);
      n += 1;
      j += Number_2;
    }

    j = offset_ - 1;
    n = i0_ - 1;
    nn = i1_ + 1;
    while (n < nn) {
      copyX[j] -= gamma * (copyX[j - 1] + copyX[j + 1]);
      n += 1;
      j += Number_2;
    }

    j = offset_;
    n = i0_;
    nn = i1_ + 1;
    while (n < nn) {
      copyX[j] -= beta * (copyX[j - 1] + copyX[j + 1]);
      n += 1;
      j += Number_2;
    }

    j = offset_ + 1;
    n = i0_;
    nn = i1_;
    while (n < nn) {
      copyX[j] -= alpha * (copyX[j - 1] + copyX[j + 1]);
      n += 1;
      j += Number_2;
    }
  }
}

class ReversibleTransform extends Transform {
  constructor() {
    super();
  }

  filter(y: number[], offset: number, length: number, i0: number, x: number[]): void {
    let copyX: number[] = x;
    let i0_:number = Math.floor(i0 / Number_2);
    let i1_:number = Math.floor((i0 + length) / Number_2);
    let offset_:number = offset - (i0 % 1);
    let n: number = i0_;
    let nn: number = i1_ + 1;
    let j: number = offset;
    while (n < nn){
      copyX[j] = y[j] - Math.floor((y[j - 1] + y[j + 1] + Number_2) / Number_4);
      n += 1;
      j += Number_2;
    }
    n = i0_;
    nn = i1_;
    j = offset_ + 1;
    while (n < nn) {
      copyX[j] = y[j] + Math.floor((copyX[j - 1] + copyX[j + 1]) / Number_2);
      n += 1
      j += Number_2
    }
  }
}
class Bidi {
  private static baseTypes: string[] = [
    'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'S', 'B', 'S', 'WS', 'B', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'B', 'B', 'B', 'S', 'WS', 'ON', 'ON', 'ET', 'ET', 'ET', 'ON', 'ON', 'ON', 'ON', 'ON', 'ON', 'CS', 'ON', 'CS', 'ON', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'ON', 'ON', 'ON', 'ON', 'ON', 'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'ON', 'ON', 'ON', 'ON', 'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'ON', 'ON', 'ON', 'ON', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'B', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'CS', 'ON', 'ET', 'ET', 'ET', 'ET', 'ON', 'ON', 'ON', 'ON', 'L', 'ON', 'ON', 'ON', 'ON', 'ON', 'ET', 'ET', 'EN', 'EN', 'ON', 'L', 'ON', 'ON', 'ON', 'EN', 'L', 'ON', 'ON', 'ON', 'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L'
  ];

  private static arabicTypes: string[] = [
    'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'CS', 'AL', 'ON', 'ON', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'ET', 'AN', 'AN', 'AL', 'AL', 'AL', 'NSM', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'ON', 'NSM', 'NSM', 'NSM', 'NSM', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL'
  ];

  private static isOdd(i: number): boolean {
    return (i & 1) !== 0;
  }

  private static isEven(i: number): boolean {
    return (i & 1) === 0;
  }

  private static findUnequal(arr: string[], start: number, value: string): number {
    let j: number = start;
    for (j = start; j < arr.length; ++j) {
      if (arr[j] !== value) {
        return j;
      }
    }
    return j;
  }
  private static setValues(arr: string[], start: number, end: number, value: string): void {
    let copyArray: String[] = arr;
    for (let j = start; j < end; ++j) {
      copyArray[j] = value;
    }
  }
  private static reverseValues(arr: string[], start: number, end: number): void {
    let copyArray: String[] = arr;
    let i:number = start;
    let j:number = end - 1;
    while (i < j){
      let temp:String = copyArray[i];
      copyArray[i] = copyArray[j];
      copyArray[j] = temp;
      i += 1;
      j -= 1;
    }
  }
  private static mirrorGlyphs(c: string): string {
    switch (c) {
      case '(':
        return ')';
      case ')':
        return '(';
      case '<':
        return '>';
      case '>':
        return '<';
      case ']':
        return '[';
      case '[':
        return ']';
      case '}':
        return '{';
      case '{':
        return '}';
      case '\u00AB':
        return '\u00BB';
      case '\u00BB':
        return '\u00AB';
      default:
        return c;
    }
  }
  constructor() {
  }

  public static bidi(text: BidiText, startLevel: number): string {
    let copyText: BidiText = text;
    let copyStartLevel: number = startLevel;
    const str:string = text.str;
    const strLength:number = str.length;
    if (strLength === 0) {
      return str;
    }
    let chars: string[] = new Array<string>();
    let types: string[] = new Array<string>();
    let oldtypes: string[] = new Array<string>();
    let numBidi:number = 0;
    for (let i = 0; i < strLength; ++i) {
      chars[i] = str.charAt(i);
      const charCode:number = str.charCodeAt(i);
      let charType:string = 'L';
      if (charCode <= Number_0x00ff) {
        charType = Bidi.baseTypes[charCode];
      } else if (0x0590 <= charCode && charCode <= Number_0x05f4) {
        charType = 'R';
      } else if (0x0600 <= charCode && charCode <= Number_0x06ff) {
        charType = Bidi.arabicTypes[charCode & Number_0xff];
      } else if (0x0700 <= charCode && charCode <= Number_0x08AC) {
        charType = 'AL';
      }
      if (charType === 'R' || charType === 'AL' || charType === 'AN') {
        numBidi += 1;
      }
      oldtypes[i] = charType;
      types[i] = charType;
    }
    if (numBidi === 0) {
      copyText.direction = 'ltr';
      return str;
    }

    if (startLevel === -1) {
      if ((strLength / numBidi) < 0.3) {
        copyText.direction = 'ltr';
        copyStartLevel = 0;
      } else {
        copyText.direction = 'rtl';
        copyStartLevel = 1;
      }
    }

    let levels: number[] = new Array<number>();
    for (let i = 0; i < strLength; ++i) {
      levels[i] = copyStartLevel;
    }

    const e: string = Bidi.isOdd(startLevel) ? 'R' : 'L';
    const sor: string = e;
    const eor: string = sor;
    let lastType:string = sor;
    for (let i = 0; i < strLength; ++i) {
      if (types[i] === 'NSM') {
        types[i] = lastType;
      } else {
        lastType = types[i];
      }
    }
    lastType = sor;
    for (let i = 0; i < strLength; ++i) {
      const t: string = types[i];
      if (t === 'EN') {
        types[i] = (lastType === 'AL') ? 'AN' : 'EN';
      } else if (t === 'R' || t === 'L' || t === 'AL') {
        lastType = t;
      }
    }
    for (let i = 0; i < strLength; ++i) {
      const t:string = types[i];
      if (t === 'AL') {
        types[i] = 'R';
      }
    }
    for (let i = 1; i < strLength - 1; ++i) {
      if (types[i] === 'ES' && types[i - 1] === 'EN' && types[i + 1] === 'EN') {
        types[i] = 'EN';
      }
      if (types[i] === 'CS' && (types[i - 1] === 'EN' || types[i - 1] === 'AN') && types[i + 1] === types[i - 1]) {
        types[i] = types[i - 1];
      }
    }
    for (let i = 0; i < strLength; ++i) {
      if (types[i] == "EN") {
        let j:number = i - 1
        while (j >= 0) {
          if (types[j] !== "ET") {
            break
          }
          types[j] = "EN"
          j -= 1
        }
        let k:number = i + 1
        while (k < strLength) {
          if (types[k] !== "ET") {
            break
          }
          types[k] = "EN"
          k += 1
        }
      }
    }
    for (let i = 0; i < strLength; ++i) {
      const t:string = types[i];
      if (t === 'WS' || t === 'ES' || t === 'ET' || t === 'CS') {
        types[i] = 'ON';
      }
    }
    lastType = sor;
    for (let i = 0; i < strLength; ++i) {
      const t:string = types[i];
      if (t === 'EN') {
        types[i] = (lastType === 'L') ? 'L' : 'EN';
      } else if (t === 'R' || t === 'L') {
        lastType = t;
      }
    }
    let i:number = 0
    while (i < strLength) {
      if (types[i] == "ON") {
        const end: number = Bidi.findUnequal(types, i + 1, "ON")
        let before:string = sor
        if (i > 0) {
          before = types[i - 1]
        }
        let after:string = eor
        if (end + 1 < strLength) {
          after = types[end + 1]
        }
        if (before !== "L") {
          before = "R"
        }
        if (after !== "L") {
          after = "R"
        }
        if (before == after) {
          Bidi.setValues(types, i, end, before);
        }
        i = end
      }
      i += 1
    }
    for (let i = 0; i < strLength; ++i) {
      if (types[i] === 'ON') {
        types[i] = e;
      }
    }
    for (let i = 0; i < strLength; ++i) {
      const t: string = types[i];
      if (Bidi.isEven(levels[i])) {
        if (t === 'R') {
          levels[i] += 1;
        } else if (t === 'AN' || t === 'EN') {
          levels[i] += Number_2;
        }
      } else {
        if (t === 'L' || t === 'AN' || t === 'EN') {
          levels[i] += 1;
        }
      }
    }
    let highestLevel:number = -1;
    let lowestOddLevel:number = Number_99;
    for (let i = 0; i < levels.length; ++i) {
      const level:number = levels[i];
      if (highestLevel < level) {
        highestLevel = level;
      }
      if (lowestOddLevel > level && Bidi.isOdd(level)) {
        lowestOddLevel = level;
      }
    }
    let level: number = highestLevel;
    while ( highestLevel >= lowestOddLevel) {

      let start:number = -1;
      for (let i = 0; i < levels.length; ++i) {
        if (levels[i] < level) {
          if (start >= 0) {
            Bidi.reverseValues(chars, start, i);
            start = -1;
          }
        } else if (start < 0) {
          start = i;
        }
      }
      if (start >= 0) {
        Bidi.reverseValues(chars, start, levels.length);
      }
      level -= 1;
    }
    let result:string = "";
    for (let i = 0; i < chars.length; ++i) {
      const ch: string = chars[i];
      if (ch !== '<' && ch !== '>') {
        result += ch;
      }
    }
    return result;
  }
}
class Metadata {
  private metaDocument: Document | string;
  private metadata: Record<string,string>;
  constructor(meta: Document | string) {
    this.metadata = {};
    let metas:Document | string = meta
    if (typeof metas === 'string') {
      metas = Metadata.fixMetadata(meta as string);
    } else if (!(metas instanceof Document)) {
      error('Metadata: Invalid metadata object');
    }
    this.metaDocument = metas;
    this.parse();
  }
  private static fixMetadata(meta: string): string {
    let regexPattern: string = ">\\376\\377([^<]+)";
    let regex: RegExp = new RegExp(regexPattern);
    let rangeArr: RegExpMatchArray = meta.match(regex);
    let modifiedMeta: string = meta;
    for (let  matchRange of rangeArr) {
      let codes: string = matchRange[1];
      return codes;
    }
    return modifiedMeta
  }
  private parse(): void {
    let doc:Document = this.metaDocument as Document;
    if (doc === null || doc === undefined){
      return;
    }
    let rdf:PDFElement = doc.documentElement;
    if (rdf.nodeName.toLowerCase() !== 'rdf:rdf') {

      rdf = rdf.firstChild ?? null;
      while (rdf && rdf.nodeName.toLowerCase() !== 'rdf:rdf') {
        rdf = rdf.nextSibling;
      }
    }
  }
  get(name: string): string | null {
    return this.metadata[name] || null;
  }

  has(name: string): boolean {
    return typeof this.metadata[name] !== 'undefined';
  }
}

/**
  * @Setup
  */
function setupPdfJS(): void {
  PdfJS_window.__resources__[pdf_file] = buffer(PdfJS_window.atob(getPDF()));
}

function runPdfJS(): void {
  PDFJS.getDocument(pdf_file).then((pdf: PDFDocumentProxy):void => {
    const canvas: Canvas = PdfJS_window.document.getElementById('canvas') as Canvas;
    const context: PDFContext = canvas.getContext('2d')!;
    const renderContext: RenderContext = new RenderContext(context);
    let renderPages = (i: number, j: number): void => {
      if (i > j){
        canvas_logs.push(context.__log__);
        return;
      }
      context.clearRect(0, 0, canvas.width, canvas.height);
      pdf.getPage(i).then((page: PDFPageProxy) => {
        renderContext.viewport = page.getViewport(1);
        canvas.height = renderContext.viewport.height;
        canvas.width = renderContext.viewport.width;
        page.render(renderContext).then(() => renderPages(i + 1, j));
      });
    };
    renderPages(1, pdf.numPages);
  });
  PdfJS_window.flushTimeouts();
}

function tearDownPdfJS(): void {

  for (let i = 0; i < canvas_logs.length; ++i) {
    let log_length: number = canvas_logs[i].length;
    let log_hash: number = hash(canvas_logs[i].join(' '));
    let expected_length: number = 36788;
    let expected_hash: number = 939524096;

    if (log_length !== expected_length || log_hash !== expected_hash) {
      let message: string = `PdfJS produced incorrect output: expected ${expected_length} ${expected_hash},got ${log_length}  ${log_hash}`;
      error(message);
    }
  }
}


function run() {
  setupPdfJS();
  let benchmark = new Benchmark();
  let startTime = ArkTools.timeInUs() / 1000;
  benchmark.runIteration();
  let endTime = ArkTools.timeInUs() / 1000;
  print('pdfjs: ms = ', endTime - startTime);
}

/**
 * @State
 * @Tags Jetstream2
 */
class Benchmark {
  /*
   *@Benchmark
   */
  runIteration() {
    runPdfJS();
  }
}

run();
function reppeadNumberCount(
  count: number,
  element: number = 0
): number[] {
  let result: number[] = new Array(count);
  for (let i = 0; i < count; i++) {
    result[i] = element;
  }
  return result;
}
function repeatStringCont(
  count: number,
  element: string = ""
): string[] {
  let result: string[] = new Array(count);
  for (let i = 0; i < count; i++) {
    result[i] = element;
  }
  return result;
}
